求 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=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要始终保持在最后,原因之前解释过了.