最大子段和

Posted wushengyang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最大子段和相关的知识,希望对你有一定的参考价值。

原博链接 

 

一. 问题适用方法

给定长度为n的整数序列,a[1....n],求 [1,n] 某个子区间 [i,j]使得 a[i]+.....+a[j] 和最大,或者求出最大的这个和。例如(-2,11,-4,13,-5,2)的最大子段和为20,所求子区间为 [2,4]。

 

二. 问题分析

1.穷举法

用两层for循环遍历所有的子区间。

//穷举法 
#include<bits/stdc++.h>
int start=0; //起始位置 
int end=0; //结束位置 
int max=0;
for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
        int sum=0;
        for(int k=i;k<=j;k++)
        sum+=a[k];
        if(sum>max){
            start=i;
            end=j;
            max=sum;
        }
    }
}

时间复杂度是 O(n^3) 。这个代码还可以做优化,实际上我们并不需要每次都重新从起始位置求和加到终点位置,可以利用之前的计算结果。

或者我们换一种穷举思路,对于起点 i ,我们遍历所有长度为 1,2,......,n-i+1 的子区间和,以求得和最大的一个,这样遍历了所有起点的不同长度的子区间,同时,对于相同起点的不同长度的子区间,可以利用前面的计算结果来计算后面的。

比如,i为起点长度为2的子区间和就等于长度为1的子区间的和加上a[i+1]即可,这样就减少了一个循环,时间复杂度 O(n^2)。

//优化

int start=0;
int end=0;
int max=0;
for(int i=1;i<=n;i++){
    int sum=0;
    for(int j=i;j<=n;j++){
        sum+=a[j];
        if(sum>max){
            start=i;
            end=j;
            max=sum;
        }
    }
} 

 

2.分治法

求子区间及最大和,从结构上是非常适合分治法的,因为所有子区间[start,end]只可能有以下三种可能:

1.在[1,n/2]这个区域

2.在[n/2+1,n]这个区域

3.起点位于[1,n/2],终点位于[n/2+1,n]

 

以上三种情况的最大值就是所求的,前两种符合子问题的递归性,所以可以递归,第三种则需要单独处理,第三种情形包括了 n/2,n/2+1两个位置,这样可以利用第二种穷举的思路求出:

1.以n/2为终点,往左移动扩张,求出和最大的一个left_max

2.以n/2+1为起点,往右移动扩张,求出和最大的一个right_max

3.left_max+right_max是第三种情况可能的最大值

 

int maxInterval(int a,int left,int right){
    if(right==left) return a[left]>0?a[left]:0;
    int center=(left+right)/2; 
    int leftMaxInterval=maxInterval(a,left,center); //左边区间的最大子段和
    int rightMaxInterval=maxInterval(a,center+1,right); //右边区间的最大子段和

    // 以下求端点分别位于不同部分的最大子段和
    
    //center开始向左移动
    int sum=0;
    int left_max=0;
    for(int i=center;i>=left;--i){
        sum+=a[i];
        if(sum>left_max)
        left_max=sum;
    } 
    //center+1开始向右移动
    sum=0;
    int right_max=0;
    for(int i=center+1;i<=right;i++){
        sum+=a[i];
        if(sum>right_max)
        right_max=sum;
    } 
    int ret=lefr_max+right_max;
    if(ret<leftMaxInterval)
    ret=leftMaxInterval;
    if(ret<rightMaxInterval)
    ret=rightMaxInterval;
    return ret;
}

 

这种算法的时间复杂度为 O(nlogn)

3.动态规划法

由于是一个连续的区间,所以可以这样思考

1.令b[j]表示以位置 j 为终点的所有子区间中和最大的一个。

2.子问题:如j为终点的最大子区间包含了位置 j-1,则以 j-1 位终点的最大子区间必然包括在其中。

3.如果 b[j-1]>0,那么显示 b[j]=b[j-1]+a[j],用之前最大的一个加上a[j]即可,因为a[j]必须包含。

4.如果 b[j-1]<=0,那么b[j]=a[j],因为既然最大,前面的负数必然不能使你更大。

//动态规划
int max=0;
int b[n+1];
int start=0,end=0;
memset(b,0,n+1);
for(int i=1;i<=n;i++){
    if(b[i-1]>0){
        b[i]=b[i-1]+a[i];
    }
    else{
        b[i]=a[i];
    }
    if(b[i]>max)
    max=b[i];
} 

时间复杂度 O(n)。

 

 

 

 

 

 

以上是关于最大子段和的主要内容,如果未能解决你的问题,请参考以下文章

线段树维护区间最大子段和

动态规划 ------最大子段和

最大子段和问题,最大子矩阵和问题,最大m子段和问题

51nod 最大子段和

SPOJ GSS3 单点修改+区间最大子段和

CODEVS 3981(求最大子段和+线段树)