思路
提交已通过。感谢大佬们的思路,直接借鉴范围缩减至sqrt(n)+1,降低时间复杂度。
另加了两个条件,进一步减少低效计算:
一 数学家发现完全数末位数字都是6或28,虽然不知道原因,但是管用。
二 完全数另一个特征:
在因子数列末尾加上完全数自身、再从中间对半劈开,则左右都是等比数列,公比为2。
文字描述比较抽象,列几行一看就明白了:
1 2 pikai 3 6
1 2 4 pikai 7 14 28
1 2 4 8 16 pikai 31 62 124 248 496
1 2 4 8 16 32 64 pikai 127 254 508 1016 2032 4064 8128
以上,1 2 3 6 分别是 2**0, 2**1, 2**2-1, (2**2-1)*2
也就是说,以2**k找出因子数列再求和,就可以得到一个潜在的完全数,而不满足这个特点的一定不是完全数,即可进一步缩减查找范围。
结构概要:
addOnePerfectNum 负责每次为 arr 列表增加一个完全数,列表复用
Step1 计算出以 2**k 为中心的对称序列,从而确定一个潜在完全数 mul
Step2 检查这个潜在完全数是否符合特点一(以‘6’或者‘28’结尾)
Step3 用完全数定义检查(找出所有真因子并求和,再与自身比较)
Step4 通过筛选确认为完全数,加入列表arr
helper 负责检查整数n是否大于目前 arr 列表中最大的完全数
如果小于则遍历arr,数出小于n的完全数个数
如果大于,调用addOnePerfectNum补充完全数,每算一个就与n比较,直到最新的完全数大于n
代码:
class Solution:
#零 arr承载已经计算出来的完全数,避免重复计算。vark承载特点二中的k值。
arr,vark = [],0
#一 由 helper 负责检查整数n是否大于 目前计算出的最大完全数
def helper(self,n:int)->int:
_arr = self.arr
#1 如果小于,则遍历现有的完全数数组,找出小于n的完全数个数
if n<_arr[-1]:
for i in range(0,len(_arr)):
if _arr[i]>n:
break
return i
#2 如果大于,调用addOnePerfectNum补充完全数,每算一个就与n比较,直到最新的完全数大于n为止
else:
while(_arr[-1]<n):
self.addOnePerfectNum()
return len(_arr) if _arr[-1]==n else len(_arr)-1
#二 由 addOnePerfectNum 负责每次为 arr 列表增加一个完全数
def addOnePerfectNum(self):
k = self.vark
while True:
#1 首先计算出以 2**k 为中心的对称序列,从而确定一个潜在完全数 mul
a,b = 2**k, 2**(k+1)-1
mul,sm = a*b, a+b
for i in range(k-1,-1,-1):
a /= 2
b *= 2
sm += a+b
#2 然后检查这个潜在完全数是否符合特点一(以‘6’或者‘28’结尾)
sm2 = 0
if sm==2*mul and (str(mul).endswith('6') or str(mul).endswith('28')):
#2.1 用完全数的定义进行检查,找出所有真因子并求和,再与自身比较。
for i in range(1,int(mul**0.5)+1):
if mul%i==0:
if i**2==mul:
sm2 += i
else:
sm2 += int(i+mul/i)
#2.2 通过本轮筛选即确认为完全数数,加入列表arr
if mul*2==sm2:
self.arr.append(mul)
self.vark = k+1
break
else:
k = k+1
else:
k = k+1
# 在输入循环以外创建实例,不然还是每次从第一个完全数开始算。
s1 = Solution()
s1.addOnePerfectNum()
while True:
try:
n = int(input())
print(s1.helper(n))
except:
break
效果还行