给定(可能有负的)整数A1,A2,…,AN,求Ai,…,Aj和的最大值。
例如:输入4,-3,5,-2,-1,2,6,-2,最大子序列和为11(从A1到A7)。
算法1:最直观的算法,穷举式地尝试所有可能。下标变量i表示子序列的开始位置,j表示结束位置,每次选定一个子序列Ai--Aj,再使用k遍历该子序列求子序列的和。代码如下:
public static int maxSubSum1(int a[]){ ????????int maxSum=0; ???????? ????????for(int i=0;i<a.length;i++) ????????????for(int j=i;j<a.length;j++){ ????????????????int thisSum=0; ???????????????? ????????????????for(int k=i;k<=j;k++) ????????????????????thisSum+=a[k]; ???????????????? ????????????????if(thisSum>maxSum){ ????????????????????maxSum=thisSum; ????????????????} ????????????} ????????return maxSum; } |
?
复杂度分析:最外层的for循环大小为N,第二个for循环大小为N-i,假设为最坏的情况即为N,这可能会让最终的界有些大,但不影响结果。第三个循环的大小为j-i+1,我们也假设为N,因此总数为O(1·N·N·N)=O(N3)。
算法2:从算法1可看出,j其实是从i递增的,因而在内部再遍历一次多余,所以可撤除最内层的for循环来避免三次的运行时间,显然改进后的算法复杂度为O(N2),代码如下:
public static int maxSubSum2(int[] a){ ????????int maxSum=0; ????????for (int i = 0; i < a.length; i++) { ????????????int thisSum=0; ????????????for(int j=i;j<a.length;j++){ ????????????????thisSum+=a[j]; ????????????????if(thisSum>maxSum) ????????????????????maxSum=thisSum; ????????????} ????????} ????????return maxSum;???? ????} |
?
算法3:考虑使用分治法解决该问题。最大子序列出现的位置可能有3种情况,或者全部出现在左半部,或者全部出现在右半部,或者跨越中部从而位于左右两半部分之中。前两种情况可以递归求解,第三种情况可以通过求出前半部分(包括前半部分最后一个元素)的最大和以及后半部分(包括后半部分第一个元素)的最大和,将两者相加即可得到。
public static int maxSubSum3(int[] a){ ????????return maxSumRec(a, 0, a.length-1); ????} ???? ? ????private static int maxSumRec(int[] a,int left,int right){ ???????? ????????if(left==right) //base class ????????????if(a[left]>0){ ????????????????return a[left]; ????????????}else{ ????????????????return 0; ????????????} ????????int center=(left+right)/2; ????????//左边和右边最大值,递归求解 ????????int maxLeftSum=maxSumRec(a, left, center); ????????int maxRightSum=maxSumRec(a, center+1, right); ???????? ????????int maxLeftBorderSum=0,leftBorderSum=0; ????????//累加求左边界和 ????????for(int i=center;i>=left;i--) ????????{ ????????????leftBorderSum+=a[i]; ????????????if(leftBorderSum>maxLeftBorderSum){ ????????????????maxLeftBorderSum=leftBorderSum; ????????????} ????????} ???????? ????????int maxRightBorderSum=0,rightBorderSum=0; //累加求右边界和 ????????for(int i=center+1;i<=right;i++){ ????????????rightBorderSum+=a[i]; ????????????if(rightBorderSum>maxRightBorderSum){ ????????????????maxRightBorderSum=rightBorderSum; ????????????} ????????} ???????? ????????return max3(maxLeftSum,maxRightSum, maxLeftBorderSum+maxRightBorderSum); ????} private static int max3(int a,int b,int c){ ????????int max; ????????max=a>b?a:b; ????????max=max>c?max:c; ????????return max; ????} |
?
算法分析:在遍历求maxLeftBorderSum和maxRightBorderSum时,两个for循环合起来花费的时间为O(N),而在递归求maxLeftSum和maxRightSum时,实质是在分别求解N/2的子序列问题(假设N是偶数),所以每个递归花费T(N/2)个时间单元,因此有递归方程:T(1)=1,T(N)=2T(N/2)+O(N)。因此有,T(2)=4=2*2,T(4)=12=4*3,T(8)=32=8*4,以及T(16)=80=16*5,可以推出,若N=2k,有T(N)=N*(k+1)=NlogN+N=O(NlogN)。(当N不是2的幂时,分析会复杂些,但是大O的结果是不变的)
?
算法4:可以发现,负数不可能为最优子序列(和最大的子序列)的起点,因此可以推出,任何负的子序列(和为负,形如[4,-5])不可能是最优子序列的前缀。如果在内循环中检测到从a[i]到a[j]的子序列是负的,那么可以把它一直推进到j+1,如6,1,2,-10,1,可以推进到-10后的1,舍弃6到-10,在程序中通过将thisSum置零实现推进。该算法复杂度为O(N),很明显可以看出。
public int maxSubSum4(int[] a){ ????????int maxSum=0,thisSum=0; ????????for(int i=0;i<a.length;i++){ ????????????thisSum+=a[i]; ???????????? ????????????if(thisSum>maxSum){ ????????????????maxSum=thisSum; ????????????}else if(thisSum<0){ ????????????????thisSum=0;???? ????????????}???????????? ????????} ????????return maxSum; ????} |
?
总结:算法1、2想法简单粗暴,复杂度较高,算法3需要理解递归分治的思想,算法4需要一定的技巧,是很多聪明算法的典型,而且算法4只需对数据进行一次扫描,一旦a[i]被读入且被处理后,就不再需要被记忆,在任意时刻,算法4能对已经读入的数据给出子序列问题的正确答案(其他算法不具有该特性),具有这种特性的算法叫做联机算法(on-line algorithm)。仅需常量空间并以线性时间运行的联机算法几乎是完美的算法。
注:本文参考自Mark Allen Weiss的Data Structures and Algorithm Analysis in Java.
最大子序列和(Max Subsequence Sum)问题求解
原文:http://www.cnblogs.com/damon-ly/p/4415762.html