damonly

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

最大子序列和(Max Subsequence Sum)问题求解

给定(可能有负的)整数A1A2,…,AN,求Ai,…,Aj和的最大值。

例如:输入4-35-2-126-2,最大子序列和为11(从A1A7)。

算法1:最直观的算法,穷举式地尝试所有可能。下标变量i表示子序列的开始位置,j表示结束位置,每次选定一个子序列Ai--Aj,再使用k遍历该子序列求子序列的和。代码如下:

publicstaticint 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),代码如下:

publicstaticint 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种情况,或者全部出现在左半部,或者全部出现在右半部,或者跨越中部从而位于左右两半部分之中。前两种情况可以递归求解,第三种情况可以通过求出前半部分(包括前半部分最后一个元素)的最大和以及后半部分(包括后半部分第一个元素)的最大和,将两者相加即可得到。

publicstaticint maxSubSum3(int[] a){

        return maxSumRec(a, 0, a.length-1);

    }

    privatestaticint 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);

    }

privatestaticint max3(int a,int b,int c){

        int max;

        max=a>b?a:b;

        max=max>c?max:c;

        return max;

    }

 

算法分析:在遍历求maxLeftBorderSummaxRightBorderSum时,两个for循环合起来花费的时间为O(N),而在递归求maxLeftSummaxRightSum时,实质是在分别求解N/2的子序列问题(假设N是偶数),所以每个递归花费T(N/2)个时间单元,因此有递归方程:T(1)=1T(N)=2T(N/2)+O(N)。因此有,T(2)=4=2*2T(4)=12=4*3T(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,如612-101,可以推进到-10后的1,舍弃6-10,在程序中通过将thisSum置零实现推进。该算法复杂度为O(N),很明显可以看出。

publicint maxSubSum4(int[] a){

        int maxSum=0,thisSum=0;

        for(int i=0;i<a.length;i++){

            thisSum+=a[i];

            

            if(thisSum>maxSum){

                maxSum=thisSum;

            }elseif(thisSum<0){

                thisSum=0;    

            }            

        }

        return maxSum;

    }

 

总结:算法12想法简单粗暴,复杂度较高,算法3需要理解递归分治的思想,算法4需要一定的技巧,是很多聪明算法的典型,而且算法4只需对数据进行一次扫描,一旦a[i]被读入且被处理后,就不再需要被记忆,在任意时刻,算法4能对已经读入的数据给出子序列问题的正确答案(其他算法不具有该特性),具有这种特性的算法叫做联机算法(on-line algorithm)。仅需常量空间并以线性时间运行的联机算法几乎是完美的算法。

注:本文参考自Mark Allen Weiss的Data Structures and Algorithm Analysis in Java.

posted on 2015-04-10 21:55  damonly  阅读(1689)  评论(0)    收藏  举报