You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用递归求30个完美数仅得前4个后报错,求算法问题排查

你的递归完美数算法的问题分析

咱们来一步步拆解你代码里的问题,为啥只能算出前4个完美数就报错:

1. 递归导致的栈溢出(StackOverflowError)

这是最直接的触发报错的原因。你的代码用递归方式不断调用getPerfectNumbers,每一次调用都会在JVM的栈内存中创建一个新的栈帧。而完美数的增长速度快得惊人:

  • 第1个:6
  • 第2个:28
  • 第3个:496
  • 第4个:8128
  • 第5个:33550336

要找到第5个完美数,你需要从8129开始一直循环到33550336,每一次循环都会触发一次递归调用——这意味着栈里要堆积超过3000万个栈帧,远远超出了JVM默认的栈内存容量,直接就会抛出StackOverflowError。递归完全不适合这种需要大量线性迭代的场景,改用循环迭代才是正确的选择,循环不会占用栈内存,可以持续运行下去。

2. 因数计算的效率极低

你的代码计算一个数的真因数和时,循环从i=1跑到i<number,时间复杂度是O(n),对于大数来说这个过程慢到离谱。其实有很多优化空间:

  • 只需要循环到sqrt(number):因为因数是成对出现的(比如6的因数1和6、2和3),找到一个小于等于平方根的因数,就能直接得到对应的另一个因数,时间复杂度可以降到O(√n)。
  • 利用偶完美数的数学性质:目前人类还没发现奇完美数,所有已知的完美数都是偶完美数,且符合欧几里得-欧拉定理——可以表示为2^(p-1)*(2^p - 1),其中2^p - 1是梅森素数。直接通过寻找梅森素数来生成完美数,效率比逐个检查每个数高得多。

3. 递归逻辑的设计瑕疵

另外,Java是值传递,虽然你的代码里out++后传递给下一次递归的逻辑没问题,但这种递归设计本身就很别扭。递归适合处理可以拆分为独立子问题的场景,而找完美数是一个线性的遍历过程,用递归属于南辕北辙,完全发挥不出递归的优势,反而带来了栈溢出的风险。

修复思路示例

把递归改成循环,同时优化因数计算逻辑。比如下面的基础循环版本:

public class PerfectNumbers {
    public static void main(String[] args) {
        int count = 0;
        long number = 2;
        while (count < 30) {
            if (isPerfect(number)) {
                System.out.println("Perfect Number " + number);
                count++;
            }
            number++;
        }
    }

    static boolean isPerfect(long num) {
        if (num <= 1) return false;
        long sum = 1; // 1是所有大于1的数的真因数
        for (long i = 2; i * i <= num; i++) {
            if (num % i == 0) {
                sum += i;
                if (i != num / i) { // 避免平方数重复累加
                    sum += num / i;
                }
            }
        }
        return sum == num;
    }
}

当然,要找到第5个及之后的完美数,这个基础版本还是会很慢,如果要找更多完美数,建议直接基于梅森素数的检测逻辑来实现。

内容的提问来源于stack exchange,提问作者Andres Salazar Lopez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:49:34