用数学的方法来写算法续

上周我写了一篇算法求解的文章 用数学的方法来写算法 文章的最后我说我这个算法的效率其实还可以提高一倍,好几天过去了,没有人发现这其中的奥秘,我今天把答案公布一下吧,说出来大家会觉得很简单,其实写算法也好,搞开发也好,技术这种东西无非就是一层窗户纸,说出来就没那么神秘。今天我就捅破这层窗户纸。

原题是这样的:

image

下面是我上篇文章的解法

设 数列的 起始数为 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,可能还得费点脑筋。我这里抛砖引玉,剩下的留给大家去发挥吧。

原文地址:https://www.cnblogs.com/eaglet/p/1737933.html