最大子序列和

问题描述

给定一个序列(整数或浮点数),求出其中连续的子序列和最大的那一个。

例:序列{-10 1 2 3 4 -5 -23 3 7 -21},其最大的连续子序列为{1 2 3 4}或{3 7},最大和为10.

算法1:穷举法

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

public static int maxSubSum(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)。仅需常量空间并以线性时间运行的联机算法几乎是完美的算法。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值