用于查找数字

时间:2018-04-09 15:45:43

标签: algorithm recursion callstack

我试图解决此代码战kata,Square into Squares

我通过了大部分测试,但有两个输入,我的算法超出了最大调用堆栈大小。

我觉得我在照顾所有的边缘条件,而且我无法弄清楚我错过了什么。



function sumSquares (n) {
  function decompose (num, whatsLeft, result) {
    if (whatsLeft === 0) return result
    else {
      if (whatsLeft < 0 || num === 0) return null
      else {
        return decompose(num-1, whatsLeft - num * num, [num].concat(result)) || decompose(num-1, whatsLeft, result)
      }
    }
  }
  return decompose(n-1, n*n, [])
}

const resA = sumSquares(50) //[1,3,5,8,49]
console.log(resA)
const resB = sumSquares(7) //[2,3,6]
console.log(resB)
const resC = sumSquares(11) //[ 1, 2, 4, 10 ]
console.log(resC)

const res1 = sumSquares(90273)
console.log(res1)
const res2 = sumSquares(123456)
console.log(res2)
&#13;
&#13;
&#13;

2 个答案:

答案 0 :(得分:1)

看起来您的代码是正确的,但有两个问题:首先,您的调用堆栈最终会达到大小&#34; num&#34; (这可能会导致大输入失败),其次,它可能会多次重新计算相同的值。

第一个问题很容易解决:您可以跳过导致num结果为whatsLeft的{​​{1}}值。像这样:

while(num * num > whatsLeft) num = num - 1;

您可以在第一个if语句后插入此内容。这也使您可以删除对否定whatsLeft的检查。作为一种风格问题,我在返回后删除了if语句的else{}个案例 - 这减少了缩进,并且(我认为)使代码更容易阅读。但这仅仅是个人品味的问题。

function sumSquares (n) {
  function decompose (num, whatsLeft, result) {
    if (whatsLeft === 0) return result;
    while (num * num > whatsLeft) num -= 1;
    if (num === 0) return null;
    return decompose(num-1, whatsLeft - num * num, [num].concat(result)) || decompose(num-1, whatsLeft, result);
  }
  return decompose(n-1, n*n, []);
}

您的测试用例会立即针对我进行这些更改,因此第二个问题(可通过记忆解决)无需解决。我也试过在codewars网站上提交它,稍微调整一下(外部函数需要调用decompose,所以外部函数和内部函数都需要重命名),所有113个测试用例都传递859ms。

答案 1 :(得分:1)

@PaulHankin的回答提供了很好的见解

让我们看sumSquares (n) n = 100000

decompose (1e5 - 1, 1e5 * 1e5, ...)

在第一帧中,

num = 99999
whatsLeft = 10000000000

哪个产生

decompose (99999 - 1, 1e10 - 99999 * 99999, ...)

第二帧是

num = 99998
whatsLeft = 199999

问题在于:num * num上面的whatsLeft 显着大于num,每次我们重复尝试新的-1时,我们每帧只减少decompose (99998 - 1, 199999 - 99998 * 99998, ...) 。没有修复任何东西,产生的下一个过程将是

num = 99997
whatsLeft = -9999500005

第三帧是

whatsLeft

了解num 显着消极的方式?这意味着在下一个值不会导致whatsLeft降至零以下之前,我们必须经常减少// [frame #4] num = 99996 whatsLeft = -9999000017 // [frame #5] num = 99995 whatsLeft = -9998800026 ... // [frame #99552] num = 448 whatsLeft = -705 // [frame #99553] num = 447 whatsLeft = 190

sumSquares (100000)

正如我们上面所看到的,只需要估计decompose的第二位数就需要近100000帧。这正是Paul Hankin描述的第一个问题。

如果我们只使用num查看num,我们也可以更容易想象它。下面,如果找不到解决方案,堆栈将增长到num大小,因此不能用于计算// imagine num = 100000 function decompose (num, ...) { ... decompose (num - 1 ...) || decompose (num - 1, ...) } 超出堆栈限制的解决方案

while

Paul的解决方案使用num循环使用循环递减num,直到guess足够小。另一种解决方案是通过找到剩余的whatsLeft

的平方根来计算下一个const sq = num * num const next = whatsLeft - sq const guess = Math.floor (Math.sqrt (next)) return decompose (guess, next, ...) || decompose (num - 1, whatsLeft, ...)
num

现在它可用于计算console.log (sumSquares(123456)) // [ 1, 2, 7, 29, 496, 123455 ] 巨大的值

console.log (sumSquares(50))
// [ 1, 1, 4, 9, 49 ]

但请注意,某些输入存在错误。解决方案的平方仍然总和到正确的数量,但它允许重复一些数字

Math.min

要强制执行严格增加的要求,我们必须确保计算的猜测仍然低于之前的值。我们可以使用const guess = Math.floor (Math.sqrt (next)) const guess = Math.min (num - 1, Math.floor (Math.sqrt (next)))

来做到这一点
console.log (sumSquares(50))
// [ 1, 1, 4, 9, 49 ]
// [ 1, 3, 5, 8, 49 ]

现在修复了错误

function sumSquares (n) {
  function decompose (num, whatsLeft, result) {
    if (whatsLeft === 0)
      return result;
      
    if (whatsLeft < 0 || num === 0)
      return null;
    
    const sq = num * num
    const next = whatsLeft - sq
    const guess = Math.min (num - 1, Math.floor (Math.sqrt (next)))
    
    return decompose(guess, next, [num].concat(result)) || decompose(num-1, whatsLeft, result);
  }
  return decompose(n-1, n*n, []);
}

console.log (sumSquares(50))
// [ 1, 3, 5, 8, 49 ]

console.log (sumSquares(123456))
// [ 1, 2, 7, 29, 496, 123455 ]

完整的程序演示

&#13;
&#13;
 Array(1, 2, 3).asInstanceOf[Array[Any]]
&#13;
&#13;
&#13;