首页 分享 Python经典练习题——求水仙花数

Python经典练习题——求水仙花数

来源:花匠小妙招 时间:2025-01-12 08:34

严格来说,我并不知道何谓“水仙花数”,因为以前读书时根本没听过这种数,也不知道这种数有什么特征。后来从事编程之后反而听说了所谓的“水仙花数”。

如果通过网络查询,则发现水仙花数的定义也不统一,比如通过baidu百科查到如下定义:

水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital invariant,PPDI)、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数(Armstrong number),水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身(例如:1^3 + 5^3+ 3^3 = 153)。

但也有资料将“水仙花数”等同于“自幂数”——只要该数的每个位上的数字的N次方(N等于该数的位数)的和等于该数即可。

不管怎样,这些不是我们关注的重点。程序员就是根据客户需求(业务规则)进行实现——规则你们来定,我们负责实现!

通过上面不难发现,判断一个数是否为水仙花数,首先要获得该数在个位、十位、百位……上的数字,然后计算这些数字的N次方,并将它们加起来即可。

先看真正“水仙花”数的简单计算方法:

基于循环来计算严格的“水仙花数”

1# 方法一

2start = 101

3end = 999

4for i in range(start, end + 1):

5    # 计算百位上的数

6    bai = i // 100

7    # 计算十位、个位上的数

8    shi, ge = (i - bai * 100) // 10, i % 10

9    # 判断是否为水仙花数

10    if ge ** len(str(i)) + shi ** len(str(i)) + bai ** len(str(i)) == i:

11        print(i)

上面算法只能计算三位的水仙花数,该算法只需要使用单层循环,并通过数学整除、求余来计算百位、十位、个位上的数,然后判断该数是否为水仙花数。

总结来说,这个算法简单、易懂、适合初学者上手学习,而且这个算法只需要单层循环;这个算法最大的问题是不适合计算多位的“自幂数”。

下面对这个方法略作改进。

基于循环来计算“自幂数”(非严格“水仙花数”)

下面方法就是可以计算任意范围(只要不超过Python整数的取值范围)的水仙花数(自幂数),下面这个算法将采用循环来计算各数位上的数值。

1# 方法二

2end = int(input('请输入最大范围:'))

3for i in range(1, end + 1):

4    # 计算数字i的长度

5    length = len(str(i))

6    sm = 0

7    temp = i

8    for j in range(length):

9        # 对于10求余等到个位上的数字,

10        # 然后计算length次方,并累加其总和

11        sm += (temp % 10) ** length

12        # 将目标数缩小10倍,下一步将会获取十位上的数字

13        # 依次类推,下一次获取百位、千位上的数

14        temp //= 10

15    # 判断是否为水仙花数

16    if sm == i:

17        print(i)

这个算法与前面算法基本相似,区别只是这个算法需要通过依次求余来获取个位、十位、百位、千位……上的数,由于程序并不知道要判断的数到底有几位,因此程序使用了循环依次求余来获取个位、十位、百位、千位……上的数。

这个算法是前一个算法的稍作改进。

此外,我们知道Python的字符串也是可迭代对象,当程序迭代字符串时,程序就可以依次获取字符串中的每个字符,因此程序可同构这种方式来获取一个数在在个位、十位、百位……上的数字。

通过遍历字符串来计算“自幂数”(非严格“水仙花数”)

下面程序只是对前一个程序的改变,本程序不再使用数学的求余、整除算法来计算个位、十位、百位……上的数字,而是通过遍历字符串来获取个位、十位、百位……上的数字。

1# 方法三

2end = int(input('请输入最大范围:'))

3for i in range(1, end + 1):

4    # 计算数字i的长度

5    length = len(str(i))

6    sm = 0

7    # 将i转成字符串,然后通过遍历字符串来依次获取每位数字

8    for j in str(i):

9        sm += (ord(j) - 48) ** length

10    # 判断是否为水仙花数

11    if sm == i:

12        print(i)

这个算法与前一个算法的区别在于计算计算个位、十位、百位……上的数字的方法不同,本程序采用的是遍历字符串的方式进行计算。

此外,Python还提供了一个sum()函数来计算列表的总和,因此程序可以将各数位上的值的N次方收集成一个列表,然后利用sum()函数来计算该列表的总和,这样就可判断该数是否为水仙花数了。

利用列表推导式来计算“自幂数”(非严格“水仙花数”)

下面采用一个嵌套的列表推导式来计算水仙花数(自幂数)。

1# 方法四

2end = int(input('请输入最大范围:'))

3lt =[j for j in range(1, end + 1) if sum([(ord(i) - 48) ** len(str(j)) for i in str(j)]) == j]

4print(lt)

从上面代码可以看到,该程序只要一行就可以计算所有水仙花数(自幂数),该程序的本质是一个嵌套循环——只不过它是嵌套的列表推导式。

首先列表推导式的语法是:

for表达式用于利用其他区间、元组、列表等可迭代对象创建新的列表,for表达式语法格式如下:

[表达式 for 循环计数器 in 可迭代对象]

由于上面列表推导式存在嵌套,因此我们先看一层,如果将上面推倒使式写成如下形式:

lt =[j for j in range(1, end + 1) if j % 2 == 0]

此时该列表内将会收集从1~end的所有偶数(根据if j % 2 == 0),此时该列表推导式只有一层,并没有嵌套。

但我们并不是要简单地收集偶数,而是要收集水仙花数(自幂数),因此程序还得搞一个列表,该列表的元素是个位、十位、百位……上数字的N次方。

如何获取一个数的个位、十位、百位……上数字的N次方呢?前面已经介绍了,使用循环来遍历字符串即可。假如目标数字是j,那下面代码即可获取数值j在个位、十位、百位……上数字的N次方。

[(ord(i) - 48) ** len(str(j)) for i in str(j)]

再回头看到前面的列表推导式,它的完整格式其实就是:

[j for j in range(1, end + 1) if sum(xxx) == j]

只不过它的xxx就是[(ord(i) - 48) ** len(str(j)) for i in str(j)]。

这个算法比较简洁,只要一行代码即可计算使用列表获取指定范围的所有水仙花数,但有些初学者会反应这个列表推导式不容易看懂,这可能也是这个算法的一个问题:一般来说,我们并不建议使用多层嵌套的列表推导式,因此这样会降低程序的可读性。毕竟,对于实际企业开发来说,程序可读性才是第一位的。

上面这些算法来计算10的5次方以内的“自幂数”时,能拥有较高的效率,但一旦要计算10的8次方、甚至10的20次方以内的“自幂数”时,程序效率会变得非常低——这是由于程序本身采用是循环来判断每个数字,这种循环本身有性能开销,因此效率较低。

高效计算 “自幂数”(非严格“水仙花数”)

下面介绍一种较为高效的算法,这个算法利用了列表来减少计算,程序将“存放数字0-9的num次方的N倍(代表出现次数)的值”使用列表保存下来,这样可避免每次都要重新计算数字0-9的num次方。

此外,该算法还利用了一种预检查的方法来快速排除不符合条件的目标数,这样能更快地加速自幂数的查找效率。

该程序代码如下。

1# 方法五(高效)

2def narcissus_num(num):

3    # results 存放找到的自幂数

4    results = [] 

5    # 定义一个长度为10的列表

6    selected = [0] * 10

7    # power_of_10列表依次保存[0, 10, 100, 1000, 10000, ...]

8    # 该列表中的元素都是10的N次方

9    power_of_10 = [10 ** i for i in range(num + 1)]

10    # pre_table1 存放数字0-9的num次方的N倍(代表出现次数)的值

11    # 例如num为6,pre_table1的元素依次为

12    # [[0**6 * 0, 0**6 * 1, ...0**6 * 6], [1**6 * 0, 1**6 * 1, 1**6 * 2, ...]]

13    pre_table1 = [[i ** num * j for j in range(num + 1)] for i in range(10)]

14    # pre_table2是一个长度为10的列表,每个列表元素又是一个长度为num+1、元素为0的列表

15    pre_table2 = [[0] * (num + 1) for i in range(10)]

16    # num位的自幂数应该在power_of_10[num - 1](10**num-1)~power_of_10[num](10**num)之间

17    min_num = power_of_10[num - 1]

18    max_num = power_of_10[num]

19    # 对pre_table2进行初始化,让它存放pre_table1中各个值除首位外的位数

20    for i in range(10):

21        for j in range(num + 1):

22            for k in range(num, 0, -1):

23                if power_of_10[k] < pre_table1[i][j]:

24                    pre_table2[i][j] = k

25                    break

26

27    # 检查value是否为自幂数

28    def check_narcissus(value):

29        bit_result = bit_count(value)

30        for i in range(10):

31            if bit_result[i] != selected[i]:

32                return False

33        return True

34

35    # 统计value中数字的个数,返回形如[0, 2, 2, 0, 0, 0, 0, 0, 0, 0]的列表,

36    # 列表中每个元素依次代表value中0、1、2、3、4...的出现次数

37    def bit_count(value):

38        # 定义一个长度为10的列表

39        bit_result = [0] * 10

40        # 依次遍历每个位上的数,并用bit_result列表保存每个数位上的数字的出现次数。

41        for i in str(value):

42            bit_result[int(i)] += 1

43        # 假如最后返回[0, 2, 2, 0, 0, 0, 0, 0, 0, 0]

44        # 那代表该数中1出现2次、2出现2次

45        return bit_result

46

47    def pre_check(cur_index, sum, remain_num):

48        cur_big = pre_table1[cur_index][remain_num]

49        # 如果sum比当前数剩余最大可能数小,说明还有可能找到

50        if sum < cur_big:

51            return True

52        max = sum + cur_big

53        # 去掉cur_big的位数

54        max = max // power_of_10[pre_table2[cur_index][remain_num]]

55        sum = sum // power_of_10[pre_table2[cur_index][remain_num]]

56        # 去掉max,sum不同的尾部。

57        while not max == sum:

58            max = max // 10

59            sum = sum // 10

60        # max,sum头部没有相同部分。

61        if max == 0:

62            return True

63        bit_result = bit_count(max)

64        # 判断大于cur_index 的所有已确定数是否在正常范围。

65        for i in range(9, cur_index, -1):

66            if bit_result[i] > selected[i]:

67                return False

68        # 判断bit_result中小于cur_index的数(从9到0还没有判断的数)的数量是否大于remain_num

69        for i in range(cur_index + 1):

70            remain_num -= bit_result[i]

71         # 小于remain_num,属正常,返回True。

72        return remain_num >= 0

73

74    def search_num(cur_index, sum, remain_num):

75        # 如果sum已经大于max_num最大值,说明已经不可能了,直接返回

76        if sum > max_num:

77            return

78        # 如果sum加上cur_index的num次方remain_num倍,依然小于min_num最小值

79        # 说明已经不可能了,直接返回

80        if (sum + pre_table1[cur_index][remain_num]) < min_num:

81            return

82

83        # 如果不符合预检查,直接跳过

84        if not pre_check(cur_index, sum, remain_num):

85            return

86

87        if remain_num == 0:

88            # 如果检查sum符合自幂数特征,将该数添加到results列表中

89            if sum > min_num and check_narcissus(sum):

90                results.append(sum)

91            return

92

93        if cur_index == 0:

94            selected[0] = remain_num

95            # 递归调用search_num

96            search_num(-1, sum, 0)

97        else:

98            for i in range(remain_num + 1):

99                selected[cur_index] = i

100                search_num(cur_index - 1, sum + pre_table1[cur_index][i], remain_num - i)

101        # 对selected[cur_index]进行复位

102        selected[cur_index] = 0

103

104    # 设定初始值调用search_num,然后返回结果。

105    search_num(9, 0, num)

106    return results

107

108num = int(input('请输入要计算几位的自幂数:'))

109print('%d位的自幂数有:%s' % (num, narcissus_num(num)))

另外本人还开设了个人公众号:JiandaoStudio ,会在公众号内定期发布行业信息,以及各类免费代码、书籍、大师课程资源。

                                            

扫码关注本人微信公众号,有惊喜奥!公众号每天定时发送精致文章!回复关键词可获得海量各类编程开发学习资料!

例如:想获得Python入门至精通学习资料,请回复关键词Python即可。

相关知识

每日经典算法题(三) 求水仙花数
【Python 123】水仙花数玫瑰数
python求水仙花数和完数
python输出1到1000内的水仙花数
python水仙花数的编程讲解
Python水仙花、冥函数、质因数、完全数案例练习题!
python水仙花数的编程代码
LintCode Python 水仙花数(当n >= 6时报错)
python判断三位数水仙花数
Python(水仙花数)

网址: Python经典练习题——求水仙花数 https://www.huajiangbk.com/newsview1551280.html

所属分类:花卉
上一篇: 1201 简单题之水仙花数
下一篇: 范岗镇樟枫村:金桂开花满园香 乡

推荐分享