上周我写了一篇算法求解的文章 用数学的方法来写算法 文章的最后我说我这个算法的效率其实还可以提高一倍,好几天过去了,没有人发现这其中的奥秘,我今天把答案公布一下吧,说出来大家会觉得很简单,其实写算法也好,搞开发也好,技术这种东西无非就是一层窗户纸,说出来就没那么神秘。今天我就捅破这层窗户纸。
原题是这样的:
下面是我上篇文章的解法
设 数列的 起始数为 n ,从 n 开始 累加到 n + m 后的总和为 s
其中 m 为大于0 的正整数。
则 s = (n + n + m) * (m+1) / 2;
这道题我们需要已知 s 和 m 的情况下求n
那么 n = s / (m+1) – m / 2;
对分子,分母合并后 n = (2 * s – m * (m+1))/(2*(m+1));
到这里,我们要解决的问题就是找到所有使等式右边可以整除的 m 就可以了。
原文作者的思路和这个应该差不多,只是实现逻辑上有些繁琐,而且他是m从 1 到 s/2 这样穷举,
其实我从上面的公式可以看出,这里m 和 n 是成反比的,随着m的增加,n会逐渐减小,当n 小于1时,我们就可以终止这个运算。
下面我给出根据这个公式推导出来的算法实现的代码
static void GetAllSerials(int s)
{
int n = 0;
int m = 1;
do
{
int up = 2 * s - m * (m + 1); //分母
int down = 2 * (m + 1); //分子
n = up / down;
if ((up % down) == 0 && n > 0) //判读是否可以除尽,且 n 要大于0
{
for (int i = n; i <= n + m; i++)
{
if (i == n)
{
Console.Write(string.Format("{0}", i));
}
else
{
Console.Write(string.Format("+{0}", i));
}
}
Console.WriteLine();
}
m++;
}
while (n > 0);
}
计算 GetAllSerials(27545); 结果是:
13772+13773
5507+5508+5509+5510+5511
3932+3933+3934+3935+3936+3937+3938
2750+2751+2752+2753+2754+2755+2756+2757+2758+2759
1961+1962+1963+1964+1965+1966+1967+1968+1969+1970+1971+1972+1973+1974
770+771+772+773+774+775+776+777+778+779+780+781+782+783+784+785+786+787+788+789+
790+791+792+793+794+795+796+797+798+799+800+801+802+803+804
359+360+361+362+363+364+365+366+367+368+369+370+371+372+373+374+375+376+377+378+
379+380+381+382+383+384+385+386+387+388+389+390+391+392+393+394+395+396+397+398+
399+400+401+402+403+404+405+406+407+408+409+410+411+412+413+414+415+416+417+418+
419+420+421+422+423+424+425+426+427+428
当计算 27545 时,我的算法循环次数为 235 次
进一步优化
下面我就来谈谈如何进一步优化。其实上周那篇文章的评论中已经有人发现了奇数和偶数的问题,但还是没有深入的进行推导演化,最终无法解开这个谜团。
首先我们先看 m 为 奇数时,即 m = 2k + 1 (k>=0) ,将 2k+1 带入上面我们已经导出的公式
s = (n + n + m) * (m+1) / 2; 则
s = (2n + 2k + 1) * (2k + 2) /2 = (2n + 2k +1)*(K+1) = 2(n+k)*(k+1) + k + 1
由于 2(n+k)*(k+1) 肯定是偶数,这时我们发现当 k 为 奇数时 s 肯定是偶数,而k为偶数时,s肯定是奇数
由于 s 是已知的,所以我们只需要遍历 m为3,7,11 … 或者m 为 1,5,9… 这两种序列中的一种序列。这个规律诺贝尔在上一篇的回复中已经发现,可以减少运算量1/4。
然后我们再看 m 为偶数的情况,即 m = 2(k+1) (k >= 0)
s = (2n + 2(k+1))*(2(k+1)+1)/2 = (n + k + 1) * (2k + 3)
这时我们发现,m 为偶数时,s的奇偶情况由n 和 k 共同决定,这种情况,我们用上面分析m为奇数时的思路已经不好用,我们必须换一种考虑问题的方法。
我们发现要使n 为一个整数值, s 必须可以被2k+3整除,也就是说 s 必须能被大于等于3的奇数整除,我们可以把问题转换为如何快速的找到这些可以被s整除的奇数上。
如何快速找到这些奇数呢?这个地方又要用到一些数学知识了。
我们知道任何一个大于等于3的奇数都可以分解为1个或n个大于等于3的质数的乘积。
比如 3 = 3 , 5 = 5, 7=7, 9 = 3*3, 11=11,13=13,15=3*5
我上面说的这个定理是很容易证明的,证明过程我就不写了,有兴趣的人可以自己去证明。
这样我们的问题就化解为寻找这个质数的组合,把一个大于等于3的奇数分解为n个大于等于3的质数的乘积,其中n > 0
质数在整数序列中占的比例很小,这可以让我们减少大量的计算量。(素数我们可以预先找出来放到一个数组中)
找到这个质数的组合后,我们从小到大来相乘,并设置一个上限,这个上限诺贝尔 已经推导出是 2s 的平方根,这样可以以非常小的计算量算出所有满足条件的m。
这里我就不给出具体实现了,我给出了思路,有兴趣的同学可以自己去实现,如何快速找到这个素数组合,如何快速通过这个组合找到有效的m,可能还得费点脑筋。我这里抛砖引玉,剩下的留给大家去发挥吧。