Maximum Subarray-最大连续子数组和

Maximum Subarray


1.问题描述:


给定一个整型数组A[],找出数组中连续子串的最大和。


例如:给定数组【-2,1,-3,4,-1,2,1,-5,4】,和最大的连续子串为【4,-1,2,1】



2.题目分析


题目只需要找到最大和,不需要返回连续子串。有两种思路如下:


解法1:用O(n)的方法,遍历一遍数组得到结果
【思路】:
首先联想到栈操作,可以遍历一遍数组进行栈操作,具体操作及触发条件如下:sum为已有和
1)入栈:如果sum+A[i]>0则新sum对于后面元素有意义的前缀,此时将A[i]入栈,更新sum值和max值
2)出栈:如果sum+A[i]<=0则新sum对于后面元素无意义,应全部删除,则将此时栈中元素全部弹出,sum=0。(弹出的元素对max
的影响在之前步骤已经有考虑,不用担心)
另:注意严谨性。一方面,max初始化不应为0.而应该为A[i]。另一方面,单个值可能比和还大,所以在每一轮循环开始时都应该判
断,也解决了所有元素均为负的情况。
public int maxSubArray(int[] A) {
        int max=A[0];            //注意要严谨,不能初始化为0
        int sum=0;
        for(int i=0;i<A.length;i++){
            if(A[i]>max){        //注意严谨性,单个值可能比和还大,通过每一轮循环检测的方式解决
                max=A[i];
            }
            if(sum+A[i]>0){      //为正才有继续下去的必要,否则作为左前缀可以直接去掉(联想清空栈)
                sum+=A[i];
                if(sum>max){
                    max=sum;
                }
            }else{
                sum=0;           //若为负则清零,这样也可保证第一个元素一定为正
            }
        }
        return max;
    }

解法2:动态规划

【思路】:
更加简单的思路,另开一个数组b[],b[i]表示以A[i]元素结尾的最大子串的和(注意“结尾”的含义),则遍历一遍A[]的时候求得b[],b[]中最大的元素即为所求。用数组的形式保存每一步的值,比直接调用递归更加高效,省去了中间大量的重复运算。
public int maxSubArray(int[] A) {
        int max=A[0];
        int b[]=new int[A.length];
        b[0]=A[0];                          //注意初始化
        for(int i=1;i<A.length;i++){
            b[i]=A[i]>(b[i-1]+A[i])?A[i]:(b[i-1]+A[i]);  //以A[i]结尾则只需考虑两种情况
            if(b[i]>max){
                max=b[i];
            }
        }
        return max;
    }

解法3:Divide and Conquer分治

【思路】:
首先找到中间元素A[mid],有两种情况。其一,连续子数组包含中间元素,则最大和在左边数组或右边数组产生,递归即可。其二,连续子数组包含中间元素,则求得左边 数组以A[mid-1]为后缀的连续子数组最大和,求得右边以A[mid+1]为前缀的连续子数组最大和,再组合找到"左+中"、“中+右”、“左+中+右”三个中最大的,与第一种情况最大值比较
,综合最大的即为所求。
public class Solution {
    
    public int max(int a,int b){
        return a>b?a:b;
    }
    
    //分治的方法
    public int msa(int[] A,int left,int right){
        if(left==right){                  //子数组只有一个元素,则最大连续子数组和就为那个元素
            return A[left];
        }
        int mid=(left+right)/2;          //找到中间元素下标
        if(mid==left){                    //只两个元素
            return max(A[left],max(A[right],A[left]+A[right]));
        }
        int leftSum=msa(A,left,mid-1);    //左边最大和,是不考虑后缀的情况
        int rightSum=msa(A,mid+1,right);  //右边最大和,是不考虑前缀的情况
        //考虑左边后缀的情况
        int sumL=A[mid-1];
        int tmp=0;
        for(int i=mid-1;i>=left;i--){     //一轮循环找到包含A[mid-1]的最大和
            tmp+=A[i];
            if(tmp>sumL){
                sumL=tmp;
            }
        }
        
        //考虑右边前缀的情况
        int sumR=A[mid+1];                //一轮循环找到包含A[mid+1]的最大和
        tmp=0;
        for(int i=mid+1;i<=right;i++){
            tmp+=A[i];
            if(tmp>sumR){
                sumR=tmp;
            }
        }
        int sumNo=max(leftSum,rightSum); //不包mid
        int sumYes1=max(A[mid],sumL+A[mid]+sumR);
        int sumYes2=max(sumR+A[mid],sumL+A[mid]);
        return max(sumNo,max(sumYes1,sumYes2));
    }
    
    //n为数组的长度
    public int maxSubArray(int[] A) {
        if(A.length==0){
            return 0;
        }else{
            return msa(A,0,A.length-1);
        }
    }
}

3.扩展:打印最大和以及对应的连续子串。
若是解法1则在更新sum和max的同时更新子串起止下标即可。
若是解法2则traceback。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值