最大子段和
一. 问题适用方法
给定长度为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)。