Project Euler第23题Python解法错误排查:结果少995的原因分析
我尝试用Python求解Project Euler第23题,但得到的结果比正确值少995。我的最终列表未包含12、18、20和945,但这些数都是盈数,本不应出现在最终答案中。要么是我的逻辑存在严重错误,要么是题目答案有疏漏——但后者可能性极低,毕竟该题已被近20万人解决。
题目描述
若一个数n的真因数之和小于n,则称其为亏数;若该和大于n,则称其为盈数。
12是最小的盈数,其真因数之和为1+2+3+4+6=16;最小的可表示为两个盈数之和的数是24。通过数学分析可知,所有大于28123的整数都可表示为两个盈数之和。不过尽管已知无法表示为两个盈数之和的最大数小于该上限,但分析无法进一步缩小此范围。
找出所有无法表示为两个盈数之和的正整数的总和。
因数和计算代码
我确信这段因数和计算代码没问题,很多人都用类似实现:
def sum_of_divisors(n): sum = 1 for i in range(2,int(n**0.5)+1): if n%i == 0: if i == n//i: sum += i else: sum += i + n//i return sum
主逻辑代码
问题应该出在这段判断数是否为盈数的主代码中:
def find_non_abd_sum(input): sum = 1 #sum_set = set([1]) abd_lst = set([]) for n in range(2, input): if sum_of_divisors(n) > n: abd_lst.add(n) else: found = any((n-i) in abd_lst for i in abd_lst) if not found: sum += n #sum_set.add(n) return sum
我的思路
和常见解法不同,我没有生成所有已知盈数的和列表,而是仅遍历1到28123的数。核心思路是:若一个数可表示为另外两个数之和,那这两个数必然小于它,因此在遍历到该数时已经被处理过。
具体来说,若x不是盈数但可表示为两个盈数之和,则存在y,z使得y+z=x,且y和z都小于x,在遍历x前已被加入盈数集合。对于非盈数,我通过代码行any((n-i) in abd_lst for i in abd_lst)判断它是否为两个盈数之和。
问题细节
我的结果为4178876,比正确值少995。我得到的盈数数量是正确的(6975个),集合也和正确解法中的匹配。问题肯定出在find_non_abd_sum的if语句后半部分。
对比论坛、Stack Overflow甚至ChatGPT给出的正确解法,我发现它们的结果中包含12、18、20和945,而我的结果中没有。但这些数都是盈数,本不应被计入最终答案,它们的和正好是995。我完全困惑于错误原因——毕竟这些数不该被包含,但所有正确解法都包含它们。
附:这个问题从未被问过。其一,我见过的所有Stack Overflow上的正确解法(无论语言)都包含这四个数;其二,我并非询问如何优化解法速度,输入20161时我的代码仅需约0.31秒,是我见过的最快解法之一。
内容的提问来源于stack exchange,提问作者Michael Mooney

