基本算法-求最大子数组和 及其变种

  这是个非常常见的算法题,见诸于《编程之美》、《编程珠玑》等经典算法书籍(亦或,经典面试书籍:))。网上有很多关于这个问题的讨论和实现,我谨在此写下自己的理解,可能之前有人写过,但毕竟是自己思考出来的东西,权当记录一下。

  问题:一个有N个整数元素的一维数组(A[0],A[1].....,A[n-1]),这个数组当然有很多个子数组(n*n个),求最大的子数组之和。

  经典解法:

1 maxsofar=0
2   maxendinghere=0
3   for i=[0,n)
4     maxendinghere=max(maxendinghere+a[i],0)
5     maxsofar=max(maxendinghere,maxsofar)

  在此我写的是《编程珠玑》上的伪代码,我认为这个伪代码以及Bentley的解释最好的诠释了这个问题。

  Bentley写到: The key to understanding this program is the variable maxendinghere.这个算法是典型的动态规划算法,表述如下:

  我们在计算前i个数的最大子数组和时,先记录前i-1个数的最大子数组之和Maxi-1。当我们遇到新的数A[i]时,如果A[i]的加入会使得某个子数组的和大于前i-1个数的最大子数组和,则更新Maxi=该子数组的和,否则,Maxi=Maxi-1.接下来我们只需要考虑,A[i]的加入可能会使哪个子数组的和增加?当然是以i-1个数结尾的子数组。所以,在计算前i个数的最大子数组之和时,我们除了要记录前i-1个数的最大子数组之和,还应该记录以i-1个数结尾的最大子数组之和。算法如下:

  假设我们已经知道了前i-1个数组的最大子数组为A[j],....,A[k],其和为Sumk,同时我们记录以i-1为结尾的最大子数组为A[m],...,A[i-1],其和为Sumi-1,显然,Sumi-1<=Sumk

  当遇到第i个数时,如果A[i]>0,则Sumi-1+A[i]可能会大于Sumk.所以我们更新前i个数的最大子数组之和为:max{Sumi-1+A[i],Sumk}.

  然后我们还应该更新以i结尾的最大子数组之和:Sumi=max{Sumi-1+A[i],0}.亦即,如果Sumi-1+A[i]大于0,则以i结尾的最大子数组为A[m],...,A[i-1],A[i]。否则,若Sumi-1+A[i]小于等于0,则该子数组的和还不如一个空的子数组的和0,将该子数字重置为空,其和为0.


  这个经典问题有一个很类似的变种:即同样给定一个数组,写一个在其中找出不连续子数组和的最大值,也就是说子数组里的任意相邻的两个元素,在原数组里都必须是不相邻的才行。同样以数组{1, -2, 3, 5, -1, 2}为例,则和最大的不连续子数组是{1, 5, 2},函数返回值是8。

  有了第一个问题的解答,第二个问题应该很简单。我们考虑下,假设我们已经知道了前i-1个数的数组的不连续子数组和的最大值,当第i个数加入时,这个不连续子数组和在什么情况下会改变?

  第i个数加入后不能与以i-1结尾的最大不连续子数组连接,这样会违反不连续的规则。所以我们要考虑结尾最大为i-2的和最大的不连续子数组,设其和为Sumi-2.当Sumi-2+A[i]大于以第i-1个数结尾的不连续子数组和的最大值时,我们更新前i个数组成的数组的不连续子数组和的最大值为Sumi-2+A[i]。算法的代码为:

1 for(int i=2;i<length;i++){            
2     maxendingi=max(maxendingi_2+array[i],maxendingi_2,maxendingi_1);
3     int tmp=maxendingi_2;
4     maxendingi_2=max(maxendingi_1,maxendingi_2);
5     maxendingi_1=max(tmp+array[i],array[i]);
6 }

   其中maxendingi表示前i个数组成的数组的最大不连续子数组的和。

  maxendingi_2记录的是结尾最大为i-2的和最大的不连续子数组的和,初始化为array[1]。

  maxendingi_1记录的是以i-1结尾的最大的不连续子数组的和,初始化为array[2]。

  数组长度小于3的情况可以另行处理。

参考文献:

  [1] Jon Bentley 编程珠玑 p81

  [2] 面试趣题:求不连续子数组最大和的值

posted on 2012-10-04 20:42  潘的博客  阅读(643)  评论(0编辑  收藏  举报

导航