计算机的发明让很多本来很有趣的数学问题,变成了机械的穷举,让很多人遇到算法问题首先想到的就是找出所有的组合,然后穷举。没错计算机的发明确实大大加快了数学计算的速度,使我们可以计算出很多原来无法通过人工计算的数据。但计算机的速度也毕竟有限,数的数量确是无限的,如果我们遇到什么问题都去穷举,那总有一天我们会发现有些问题可能用最快的计算机算到我们头发白了都算不出来。所以在这里,我还是要呼吁一下,程序员们,让我们用数学的思维去分析算法,其实有的算法的数学逻辑真的不是太复杂,甚至就是初等代数就可以解决。下面就今天看到的这篇文章文章 一个烂算法,计算正整数被标示为连续整数的和 ,具体谈谈。
请允许我在开始这篇文章之前把原问中的题目重新贴一遍
原文中作者的方法其实也已经很不错了,只是没有数学推导,由于没有数学推导,又导致不能更好的优化。
这个问题实际上第一眼看上去就会想到等差数列求和,那么我们就来推导一下
设 数列的 起始数为 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 次,而原文作者的循环次数为 13772 次
后续思考:
这个算法其实还可以继续优化,这个算法的效率其实还可以提高1倍,大家可以想想怎么进一步优化,如果有人想出来,我就不再写了,如果没有人想出来,我过几天再公布答案。