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

求解Codewars Square into Squares kata时递归栈溢出问题排查

解决递归栈溢出问题:Square into Squares Kata优化方案

嘿,我懂你碰到的麻烦了!在做这个平方分解的kata时,递归实现虽然逻辑直观,但遇到某些需要深层递归的测试用例时,很容易触发JS的最大调用栈限制,直接溢出。咱们来一步步拆解问题,找到靠谱的解决方案~

问题根源:无剪枝的暴力递归导致栈深度爆炸

你当前的递归逻辑是每次把num减1,尝试减去num²,但这种方式在某些场景下会产生极深的递归调用链。比如当需要分解的剩余值需要很多小平方数累加时,递归层数会一路飙升,直接超过JS引擎的调用栈上限(一般在几千层左右)。

举个例子,如果剩余值是一个很大的数,而你从接近它平方根的数开始往下试,每一步都要递归到很小的数,层数自然就上去了。

优化方案1:添加剪枝逻辑,大幅减少递归深度

最直接的优化是避免无效的递归调用,我们不需要从num-1一直试到1,而是只尝试那些平方不超过剩余值的数。具体来说:

  • 每次计算当前能尝试的最大数:Math.min(num-1, Math.floor(Math.sqrt(whatsLeft)))
  • 从这个最大数往下遍历尝试,一旦找到有效路径就返回,这样能大幅压缩递归层数

修改后的代码示例:

function sumSquares(n) {
  function decompose(num, whatsLeft, result) {
    // 计算当前可尝试的最大数,避免无效递归
    const maxCandidate = Math.min(num - 1, Math.floor(Math.sqrt(whatsLeft)));
    // 从大到小尝试,更快找到有效解
    for (let i = maxCandidate; i >= 1; i--) {
      const newRemaining = whatsLeft - i * i;
      if (newRemaining === 0) {
        // 找到解,拼接结果并返回
        return [...result, i];
      } else if (newRemaining > 0) {
        // 递归尝试更小的数,注意这里传入i作为新的num(保证递减)
        const subResult = decompose(i, newRemaining, [...result, i]);
        if (subResult) return subResult;
      }
      // newRemaining < 0的情况直接跳过,不用递归
    }
    // 所有尝试都失败,返回null
    return null;
  }
  
  const rawResult = decompose(n, n * n, []);
  // 题目要求结果按从小到大排列,所以反转一下
  return rawResult ? rawResult.reverse() : null;
}

这个优化能把递归层数降到很低,因为我们只在有意义的范围内尝试,不会做无用的递归。

优化方案2:改用迭代实现彻底避免栈溢出

如果某些极端测试用例还是触发栈溢出(虽然上面的剪枝已经很少出现这种情况),可以把递归改成迭代,用手动维护的栈来保存状态:

function sumSquares(n) {
  // 栈中保存每个状态:当前最大可尝试数、剩余值、当前结果数组
  const stack = [[n, n * n, []]];
  
  while (stack.length > 0) {
    const [currentNum, remaining, currentResult] = stack.pop();
    const maxCandidate = Math.min(currentNum - 1, Math.floor(Math.sqrt(remaining)));
    
    for (let i = maxCandidate; i >= 1; i--) {
      const newRemaining = remaining - i * i;
      if (newRemaining === 0) {
        return [...currentResult, i].reverse();
      } else if (newRemaining > 0) {
        // 把新状态压入栈,继续处理
        stack.push([i, newRemaining, [...currentResult, i]]);
      }
    }
  }
  
  return null;
}

迭代版本完全不会受到JS调用栈的限制,因为我们用的是自己维护的栈数据结构,而不是函数调用栈。

测试验证

把这两个版本替换你的原代码,再去跑之前溢出的测试用例,应该就能顺利通过啦!

内容的提问来源于stack exchange,提问作者OctaviaLo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:26:31