n = 5x+2y+z

求 n = 5x + 2y + z的全部非负整数解.例如n = 5时,有4组解:(0, 0, 5)、(0, 1, 3)、(0, 2, 1)、(1, 0, 0).

1.最普通的解法,三层循环遍历:

int SolutionLoop(int n) {
  int x = n / 5;
  int y = n / 2;
  int z = n;
  int res = 0;
  for (int i = 0; i <= x; i++) {
    for (int j = 0; j <= y; j++) {
      for (int k = 0; k <= z; k++) {
        if (n == 5*i + 2*j + k) res++;
      }
    }
  }
  return res;
}

优化1:unroll 3rd loop(展开最里层的循环)

Replace
for (int k = 0; k <= z; k++) {
  if (n == 5*i + 2*j + k) res++;
}
by the following:
if (n - 5*i - 2*j >= 0) res++;

优化2:remove if(移除if判断)

Replace
for (int j = 0; j <= y; j++) {
  if (n - 5*i - 2*j >= 0) res++;
}
by the following:
for (int j = 0; j <= (n - 5*i) / 2; j++) {
  res++;
}

优化3:unroll  second loop(展开第二层循环)

step 3 : unroll 2nd loop
Replace
for (int j = 0; j <= (n - 5*i) / 2; j++) {
  res++;
}
by the following:
res += (n - 5*i) / 2 + 1;

至此已经去掉了两层循环了.代码变成了如下所示:

int unrollLoop(int n) {
  int x = n / 5;
  int res = 0;
  for (int i = 0; i <= x; i++) {
    res += (n - 5*i) / 2 + 1;
  }
  return res;
}

那么怎么展开最后一层循环呢?

可以看出:这个x的循环相当于公式:

n = 5x+2y+z

根据等差数列求和公式:可以轻松求出结果:

n = 5x+2y+z

 

如果按照这个公式写出代码,速度自然是最快的,但是如果n=100时,你发现正确结果是541,但是用上面公式算出来是546,那结果到底差在哪里呢?

 

 

 举个例子:如果计算7/2*6按顺序计算结果是18,但是如果打乱顺序7*6/2,那么结果就是21,所以用上面的公式算出来的数偏大的原因就是:把本该损失的精度给加进去了,因为原求和公式中有一个除以2,如果(n-5i)时偶数,那么没有任何问题,但是如果(n-5i)是基数,就不能随意的调整顺序了.比如:7/2 + 5/2 = 5,但是(7+5)/2 = 6,本来每部分会损失1/2的精度,但是因为调整了计算顺序导致结果偏大了.

知道了产生问题的原因,那么就很好解决了,只需要按照基数偶数分开算就好了。将(n-5i)为基数的划分一组,偶数的划分一组.但是对于任意的n,n-5i奇偶性都不定,所以按照n的奇偶性分两种情况:

1)n为奇数时:

  ①当i为奇数时,n-5i为偶数,(n-5i)/2没有精度损失

  ②当i为偶数时n-5i为基数,那么(n-5i)/2,将转换为(n-5i-1)/2

2)n为偶数时:

  ①当i为奇数时,n-5i为奇数

  ②当i为偶数时n-5i为偶数

优化4:展开最后一层循环:

int unrollAllLoop(int n) {
  int res = 0;
  int even = n / 10 + 1;
  int odd = n / 5 + 1 - even;
  if (n % 2) {
    //res +=  (n - 1 + n - (2*even - 2)*5) / 2 * even / 2;
    res += (n + 4 - 5*even) * even / 2;
    //res +=  (n - 5 + n - (2*odd - 1)*5) / 2 * odd / 2;
    res += (n - 5*odd) * odd / 2;
  } else {
    //res += ( n/2 + (n - (2*even - 2)*5)/2) * even / 2;
    res += (n + 5 - 5*even) * even / 2;
    //res += ( (n - 6)/2 + (n - 1 - (2*odd - 1)*5)/2) * odd / 2;
    res += (n - 1 - 5*odd) * odd / 2;
  }
  res += n / 5 + 1;
  return res;
}

代码即为上面按照奇偶性区分的实现,其中even是i为偶数的个数,odd是i为奇数的个数,等价于等差数列中的项数.

然后按照n的奇偶性分组,利用等差数列求和公式,即可求得两组和,然后相加及得到结果。

注意:因为已经确保求和时n-5i (或者n-5i-1)必为偶数,因此第一个除以2可以直接约掉,但是最后的除以2要始终保持在最后,原因之前解释过了.

上一篇:leetcode1217


下一篇:LeetCode 328——奇偶链表(JAVA)