最大长度的collat​​z序列 - 优化

时间:2015-12-03 11:23:51

标签: javascript node.js algorithm math optimization

我试图解决this MaxCollatzLength kata,但我正在努力优化它,以便为真正的大数字运行得足够快。

  

在这个kata中,我们将看看collat​​z序列的长度。   以及它们如何发展。写一个取整数n的函数   并返回1到n之间具有最大Collat​​z的数字   序列长度和最大长度。输出必须采取   数组的形式[number,maxLength]例如,Collat​​z序列   4是[4,2,1],3是[3,10,5,16,8,4,2,1],2是[2,1],1是[1],所以   MaxCollat​​zLength(4)应该返回[3,8]。如果n不是正数   整数,函数必须返回[]。

     

如您所见,Collat​​z序列中的数字可能超过n。最后   测试使用随机大数字,因此您可以考虑进行一些优化   你的代码:

     

你可能会非常不走运,只能得到硬数据:尝试提交2-3   如果它超时的时间;如果它仍然存在,可能你需要优化   你的代码更多;

     

优化1:计算a的长度时   顺序,如果n是奇数,那么3n + 1是多少?

     

优化2:当循环1到n时,取i使得i <不,2,什么   将是2i的序列长度?

递归解决方案可以快速打击堆栈,因此我使用了while循环。我想我已经理解并应用了第一个优化。我还发现,对于n是2的幂,最大长度将是(log2 of n)+ 1(这对于一个非常大的数字只会刮掉很短的时间)。最后,我已经记住了到目前为止计算的折叠长度,以避免重新计算。

然而,我并不了解第二次优化的含义。我试图注意到一个带有一些随机样本和循环的模式,并且我已经绘制了n&lt; n&lt; 50000.我注意到它似乎大致跟随曲线,但我不知道如何继续 - 这是一个红鲱鱼吗?

enter image description here

我理想地寻找正确方向的提示,以便我自己努力解决方案。

function collatz(n) {
  let result = [];

  while (n !== 1) {
    result.push(n);
    if (n % 2 === 0) n /= 2;
    else {
      n = n * 3 + 1;
      result.push(n);
      n = n / 2;
    }
  }

  result.push(1);
  return result;
}

function collatzLength(n) {
  if (n <= 1) return 1;
  if (!collatzLength.precomputed.hasOwnProperty(n)) {
    // powers of 2 are logarithm2 + 1 long
    if ((n & (n - 1)) === 0) { 
      collatzLength.precomputed[n] = Math.log2(n) + 1; 
    } else {
      collatzLength.precomputed[n] = collatz(n).length;
    }
  }
  return collatzLength.precomputed[n];
}

collatzLength.precomputed = {};

function MaxCollatzLength(n) {
  if (typeof n !== 'number' || n === 0) return [];
  let maxLen = 0;
  let numeralWithMaxLen = Infinity;

  while (n !== 0) {
    let lengthOfN = collatzLength(n);

    if (lengthOfN > maxLen) {
      maxLen = lengthOfN;
      numeralWithMaxLen = n;
    }
    n--;
  }

  return [numeralWithMaxLen, maxLen];
}

1 个答案:

答案 0 :(得分:3)

记忆是这里表现良好的关键。您会记住计算Collat​​z序列的函数的最终结果。这将帮助您重复调用maxCollatzLength,但不会在第一次确定序列长度时帮助您。

另外,正如@j_random_hacker所提到的,没有必要将序列实际创建为列表;它足以存储它的长度。整数结果重量轻,可以轻松记忆。

当您确定Collat​​z序列的长度时,您可以使用预先计算的结果。不要一直按顺序执行,而是按照它直到找到已知长度的数字。

您进行的其他优化是微优化。我不确定计算两个权力的日志真的可以买到任何东西。它会给你额外的考验带来负担。

下面的memoized实现甚至放弃了检查1,最初将1放在预先计算值的字典中。

var precomp = {1: 1};

function collatz(n) {
    var orig = n;
    var len = 0;

    while (!(n in precomp)) {
        n = (n % 2) ? 3*n + 1 : n / 2;
        len++;
    }

    return (precomp[orig] = len + precomp[n]);
}

function maxCollatz(n) {
    var res = [1, 1];

    for (var k = 2; k <= n; k++) {
        var c = collatz(k);

        if (c > res[1]) {
            res[0] = k;
            res[1] = c;
        }
    }

    return res;
}

我还没有使用过node.js,而是使用了我的Firefox中的JavaScript。它提供了合理的性能。我首先使用collatz作为递归函数,这使得实现只比你的实现稍快。

问题中提到的第二个优化意味着,如果您知道C(n),那么您也知道C(2*n) == C(n) + 1。您可以使用该知识以自下而上的方式预先计算所有偶数n的值。

如果Collat​​z序列的长度可以从下往上计算,有点像Erathostenes的筛子,那将是很好的。你必须知道你来自哪里而不是去哪里,但是很难知道ehen要停下来,因为为了找到n < N的最长序列,你必须计算出许多超出界限的序列{ {1}}。因此,备忘录是一种避免重复的好方法。