编程之美——求数组的子数组之和的最大值(一维)
问题:一个有N个整数元素的一维数组(A[0]、A[1],…A[n-1]),求子数组之和的最大值。
方法一:蛮力法 时间复杂度为O(N^2).
#include<iostream>
#include<limits.h>
using namespace std;
int MaxSum(int *A,int n)
{
int maximum=INT_MIN;
int sum;
for(int i=0;i<n;i++)
{
int sum=0;
for(int j=i;j<n;j++)
{
sum+=A[j];
if(sum>maximum)
maximum=sum;
}
}
return maximum;
}
int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum(a,6)<<endl;
return 0;
}
方法二:分治法
设A[i]为数组中的一个任意元素,则对于最大连续子数组和,有:
1、若最大子数组和中包括A[i]这个元素,则从A[i]往左找,找出左边的最大值,再从A[i]往右找,找出右边的最大值,相加即得。
2、若最大子数组和中不包括A[i]这个元素,则最大子数组和是A[0],…A[i-1]和A[i+1]…A[n-1]两个子数组最大和的最大值。
时间复杂度为O(NlgN)。
#include<iostream>
#include<limits.h>
using namespace std;
int MaxSum2(int *A,int beg,int end)
{
if(beg==end)
return A[beg-1];
int mid=(beg+end)/2;
int left=MaxSum2(A,beg,mid);
int right=MaxSum2(A,mid+1,end);
int sum=A[mid-1];
int i=mid-1;
int j=mid+1;
int maxsum=A[mid-1];
while(i>=beg)
{
sum+=A[i-1];
if(sum>maxsum)
maxsum=sum;
i–;
}
sum=maxsum;
while(j<=end)
{
sum+=A[j-1];
if(sum>maxsum)
maxsum=sum;
++j;
}
int max=left>right?left:right;
max=max>maxsum?max:maxsum;
return max;
}
int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum2(a,1,6)<<endl;
return 0;
}
方法三:时间复杂度为O(N)。
1.动态规划法
//此处申请了两个数组,需要占用一定的空间。
#include<iostream>
#include<limits.h>
using namespace std;
int max(int x,int y)
{
return (x>y)?x:y;
}
int MaxSum3(int *A,int n)
{
int Start[6]={INT_MIN};
int All[6]={INT_MIN};
Start[n-1]=A[n-1];
All[n-1]=A[n-1];
for(int i=n-2;i>=0;i–)
{
Start[i]=max(A[i],A[i]+Start[i+1]);
All[i]=max(Start[i],All[i+1]);
}
return All[0];
}
int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum3(a,6)<<endl;
return 0;
}
2.改进上面的程序后,只需要O(1)的空间。
#include<iostream>
#include<limits.h>
using namespace std;
int max(int x,int y)
{
return (x>y)?x:y;
}
int MaxSum3(int *A,int n)
{
int Start=A[n-1];;
int All=A[n-1];
for(int i=n-2;i>=0;i–)
{
Start=max(A[i],A[i]+Start);
All=max(Start,All);
}
return All;
}
int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum3(a,6)<<endl;
return 0;
}
3.另一种写法
#include<iostream>
#include<limits.h>
using namespace std;
int MaxSum4(int *A,int n)
{
int Start=A[n-1];
int All=A[n-1];
for(int i=n-2;i>=0;i–)
{
if(Start<0)
Start=0;
Start+=A[i];
if(Start>All)
All=Start;
}
return All;
}
int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum4(a,6)<<endl;
return 0;
}
扩展问题1:如果考虑到数组首尾相连呢?需要按以下步骤做出改进:
1、先按不相连计算出最大值max
2、从尾往头扫描,找出最大值m1,并记录最大位置i,再从头往尾扫描,找出最大值m2, 并记录最大位置j,若i>j,则比较m1+m2与max,求出最大值,若i<=j,则令m = A[0]+A[1]+A[2]+…A[n-1],求出m和max之间的最大值。
代码为:
#include<iostream>
using namespace std;
int max(int a,int b)
{
return a>b?a:b;
}
int min(int a,int b)
{
return a<b?a:b;
}
/*********************************动态规划**************************************
假设A[0],A[1],…A(n-1)的最大子段为A[i],…,A[j],则有以下3种情况,
1)当0=i=j的时候,元素A[0]本身构成和最大的一段
2)当0=i<j的时候,和最大的一段以A[0]开头
3)当0<i时候,元素A[0]跟和最大的一段没有关系
则原始问题A[0],A[1],…A(n-1)的解All[0]=max{A[0],A[0]+Start[1],ALL[1]}
求得A[0],A[1],…A[n-1](首尾不连接)的情况后再考虑整体思路中的第二种情况
*********************************************************************************/
//从尾到首动态规划
int MaxSum5(int *A,int length){
//先求出A[0],A[1],…A[n-1](首尾不连接)的情况下子数组和最大值nAll
int nStart=A[length-1];
int nAll=A[length-1];
for(int i=length-2;i>=0;i–){
nStart=max(A[i],A[i]+nStart);
nAll=max(nStart,nAll);
}
//下面处理整体思路的第二种情况,即跨过A[n-1],A[0]
//先求A[n-1]结尾的和最大的一段(A[i],…,A[n-1])(0<=i<n)
int sum=0;
int ltempmax=-10000000;
int lpos=length;
for(int j=length-1;j>=0;j–){
sum+=A[j];
if(sum>ltempmax){
ltempmax=sum;
lpos=j;
}
}
//求A[0]开始和最大的一段(A[0],…,A[j])(0<=j<n)
sum=0;
int rtempmax=-10000000;
int rpos=-1;
for(int k=0;k<length;k++){
sum+=A[k];
if(sum>rtempmax){
rtempmax=sum;
rpos=k;
}
}
//如果lpos<=rpos,则循环数组中可能出现的子数组最大值要么是A[0]…A[n-1]子数组和的最大值nAll
//要么是整个数组A[0]…A[n-1]的和再减去A[0]…A[n-1]中子数组和为负数的最小值
if(lpos<=rpos){
//求数组中和为负数且的最小值
int minStart=0;
int minAll=0;
for(int i=0;i<length;i++){
minStart=min(0,A[i]+minStart);
minAll=min(minStart,minAll);
}
int tempmax=ltempmax+rtempmax;
for(int r=lpos;r<=rpos;r++){
tempmax-=A[r];
}
//比较A[0]…A[n-1]子数组和的最大值nAll跟A[0]…A[n-1]的和再减去A[0]…A[n-1]中子数组和为负数的最小值
return max(nAll,tempmax-minAll);
}else{
//比较A[0]+…+A[j]+A[i]+…+A[n-1]即ltempmax+rtempmax的值跟A[0]…A[n-1]子数组和的最大值nAll
return max(nAll,ltempmax+rtempmax);
}
}
int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum5(a,6)<<endl;
return 0;
}
扩展问题2:
求数组的子数组之和的最大值并给出子数组的起始终止位置?
#include<iostream>
#include<limits.h>
using namespace std;
//动态规划法
int MaxSum5(int *A,int n,int start,int end)
{
int nStart=A[n-1];
int nAll=A[n-1];
start=end=n-1;
int ostart=start;
int oend=end;
for(int i=n-2;i>=0;i–)
{
if(A[i]>A[i]+nStart)
{
start=end=i;
nStart=A[i];
}
else
{
start=i;
nStart=nStart+A[i];
}
if(nStart>nAll){
ostart=start;
oend=end;
nAll=nStart;
}
}
start=ostart;
end=oend;
cout<<start<<” “<<end<<endl;
return nAll;
}
int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum5(a,6,0,5)<<endl;
return 0;
}