如何将此O(n ^ 2)算法转换为O(n)?

时间:2017-11-07 04:47:19

标签: javascript complexity-theory

https://www.codewars.com/kata/is-my-friend-cheating/train/javascript

我的目标是设计一个函数,找到满足等式(a, b)的数字对a * b == sum(1, 2, 3 ..., n-2, n-1, n) - a - b

以下代码查找所有对,但速度太慢且超时。我在评论中看到了这个挑战,算法需要通过O(n)复杂度。这是怎么做到的?

function removeNb (n) {
  if(n===1) return null;

  let sum = (n * (n+1))/2;

  let retArr = [];
  let a = n;
  while( a !== 0){
    let b = n;
    while( b !== 0){
       if(b != a && a*b == ((sum - b) - a) ){
         retArr.push([a,b]);
       }
       b--;
    }
    a--;
  }
  retArr.sort( (a,b) => a[0] - b[0]);
  return retArr;
} 

感谢大家的帮助!这是我的最终解决方案:

function removeNb (n) {
  let retArr = [];
  let a = 1;
  let b = 0;
  let sumN = (n * (n+1))/2; 

  while( a <= n){
    b = parseInt((sumN - a) / (a + 1));
    if( b < n && a*b == ((sumN - b) - a) ) 
      retArr.push([a,b]);
    a++;
  }
  return retArr;
} 

当我尝试解决b时,我认为我的主要问题是我的代数出现了(令人尴尬的)错误。以下是任何想知道的正确步骤:

a*b = sum(1 to n) - a - b
ab + b = sumN - a
b(a + 1) = sumN - a
b = (sumN - a) / (a + 1)

3 个答案:

答案 0 :(得分:1)

你可以求解b并得到:b = (sum - a)/(a + 1)(给定一个!= -1)

现在迭代a一次 - &gt;为O(n)

答案 1 :(得分:0)

这是一个实现:

function removeNb(n){
    var sum = (1 + n) * n / 2;
    var candidates = [];

    // O(n)
    for(var y = n; y >= 1; y--){
        x = (-y + sum) / (y + 1);

        /*
         * Since there are infinite real solutions,
         * we only record the integer solutions that
         * are 1 <= x <= n.
         */
        if(x % 1 == 0 && 1 <= x && x <= n)
            // Assuming .push is O(1)
            candidates.push([x, y]);
    }

    // Output is guaranteed to be sorted because
    // y is iterated from large to small.
    return candidates;
}

console.log(removeNb(26));
console.log(removeNb(100));

https://jsfiddle.net/DerekL/anx2ox49/

根据您的问题,它还说明了

  

在该序列中,他选择两个数字a和b。

但是,它没有提及ab是唯一的数字,因此代码中不包含检查。

答案 2 :(得分:0)

如在其他答案中所解释的,可以对b求解O(n)算法。此外,考虑到解的对称性 - 如果(a,b)是解,同样(b,a)是 - 也可以保存一些迭代,一次添加几个解。要知道需要多少次迭代,让我们注意b> a当且仅当a&lt; -1 + SQRT(1个+总和)。为了证明这一点:

(sum-a)/(a+1) > a ; sum-a > a^2+a ; sum > a^2+2a ; a^2+2a-sum < 0 ; a_1 < a < a_2 

其中a_1和a_2来自2度方程解:

a_1 = -1-sqrt(1+sum) ; a_2 = -1+sqrt(1+sum)

因为a_1&lt; 0和a&gt; 0,最后我们证明了b> a当且仅当a&lt; A_2。

因此我们可以避免在-1 + sqrt(1 + sum)之后的迭代。

一个工作示例:

function removeNb (n) {
  if(n===1) return null;
  let sum = (n * (n+1))/2;
  let retArr = [];
  for(let a=1;a<Math.round(Math.sqrt(1+sum));++a) {
      if((sum-a)%(a+1)===0) {
          let b=(sum-a)/(a+1);
          if(a!==b && b<=n) retArr.push([a,b],[b,a]);
      } 
  }
  retArr.sort( (a,b) => a[0] - b[0]);
  return retArr;
}

然而,通过这种实现,我们仍然需要最终的排序。为了避免它,我们可以注意到b =(sum-a)/(a + 1)是a的递减函数(推导它来证明)。因此,我们可以构建retArr连接两个数组,一个向末尾添加元素(push),一个在开头添加元素(unshift)。一个工作的例子如下:

function removeNb (n) {
  if(n===1) return null;
  let sum = (n * (n+1))/2;
  let retArr = [];
  let retArr2 = [];
  for(let a=1;a<Math.round(Math.sqrt(1+sum));++a) {
      if((sum-a)%(a+1)===0) {
          let b=(sum-a)/(a+1);
          if(a!==b && b<=n) {
              retArr.push([a,b]);
              retArr2.unshift([b,a]); // b>a and b decreases with a
          }
      } 
  }
  retArr=retArr.concat(retArr2); // the final array is ordered in the 1st component
  return retArr;
}

作为一个非母语的人,我会说从参考文献“all(a,b)中可能删除的序列1到n中的数字”的短语意味着!= b, 所以我添加了这个约束。