问题描述:
一个有N个整数元素的一维数组(A[0],A[1],A[2],...A[n-1]),这个数组中子
数组之和的最大值是多少?该子数组是连续的。
例如 数组:[1,-2,3,5,-3,2]返回8; 数组:[0,-2,3,5,-1,2]返回9
问题求解:
#include <iostream>
#include <malloc.h>
#include <limits.h>
using namespace std;
//方法1:O(n^2) 遍历算法
//设Sum[i,...,j]为数组a中第i个元素到第j个元素的和(0<=i<=j<n)
//遍历所有的Sum[i,...,j],并且利用Sum[i,...,j]=Sum[i,....j-1]+a[j];
int MaxSum1(int *a,int n)
{//宏INT_MIN定义类型为int的最小值,包含在头文件#include <limits.h>中
int maximum = INT_MIN;
int sum;
for(int i=0;i<n;i++)
{
sum=0;
for(int j=i;j<n;j++)
{
sum+=a[j];
if(sum>maximum)
maximum=sum;
}
}
return maximum;
}
//方法2:O(nlogn) 分治算法
//如果将所给数组(A[0],...,A[n-1])分为长度相等的两段数组
//(A[0],...,A[n/2-1])和(A[n/2],...,A[n-1]),分别求出这
//两段数组各自最大子段和,则原数组(A[0],...,A[n-1])的最大子段和分
//为以下三种情况:a.(A[0],...,A[n-1])的最大子段和与
//(A[0],...,A[n/2-1])的最大子段和相同;
//b.(A[0],...,A[n-1])的最大子段和与(A[n/2],...,A[n-1])
//的最大子段和相同;c.(A[0],...,A[n-1])的最大子段跨过其中间两个
//元素A[n/2-1]到A[n/2].对应a和b两个问题是规模减半的两个相同的子
//问题,可以用递归求得。对于c,需要找到以A[n/2-1]结尾的和最大的一段
//数组和S1=(A[i],...,A[n/2-1])和以A[n/2]开始和最大的一段和S2=
//(A[n/2],...,A[j]),那么第三种情况的最大值为S1+S2。
int MaxSum2(int *a, int low, int high)
{
if(low >= high)
return a[low];
int mid = (low+high)>>1;
int MaxLeftSum=MaxSum2(a,low,mid);//情况a:左半段最大子段和
int MaxRightSum=MaxSum2(a,mid+1,high);//情况b:右半段最大子段和
//情况c第一部分:MaxSumLeftBorder为以a[mid]结尾的最大一段数组之和
int SumLeftBorder = 0, MaxSumLeftBorder = INT_MIN;
for(int i=mid;i>=low;i--)
{
SumLeftBorder += a[i];
if(SumLeftBorder > MaxSumLeftBorder)
{
MaxSumLeftBorder = SumLeftBorder;
}
}
//情况c第二部分:MaxSumRightBorder为以a[mid + 1]
//开始的最大一段数组之和
int SumRightBorder = 0, MaxSumRightBorder = INT_MIN;
for(int i=mid+1;i<=high;i++)
{
SumRightBorder += a[i];
if(SumRightBorder > MaxSumRightBorder)
{
MaxSumRightBorder = SumRightBorder;
}
}
int Maxab=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;
//情况c:最大子段跨过中间两元素a[n/2-1]、a[n/2]的最大子段和
int Maxc=MaxSumLeftBorder + MaxSumRightBorder;
return Maxab>Maxc?Maxab:Maxc;
}
//方法3:动态规划法(时间复杂度为O(N))
//将一个大问题(N个元素数组)转换为一个较小的问题(n-1个元素数组)。
//假设result[0]为已经找到数组[0,1,...n-1]中子数组和最大的,
//即保存当前找到的最大子数组。
//sum[i]为包含第i个元素且和最大的连续子数组。对于数组中的第i+1个元素
//有两种选择:a.作为新子数组的第一个元素
//b.放入前面已经找到的最大的子数组sum[i-1]中。
int MaxSum3(int *a,int n)
{
int *sum=(int *)malloc(n*sizeof(int));
int *result=(int *)malloc(n*sizeof(int));
//sum[i]为包含第i个元素且和最大的连续子数组
//result[i]保存当前找到的最大子数组
sum[0]=a[0];
result[0]=a[0];
for(int i=1;i<n;i++)
{//若a[i]>a[i]+sum[i-1],则作为新子数组的第一个元素,否则放
//入前面已经找到的最大的子数组sum[i-1]中
sum[i]=max(a[i],a[i]+sum[i-1]);
result[i]=max(sum[i],result[i-1]);
}
return result[n-1];
}
//方法4:对方法3的改进,只需O(1)的空间复杂度
int MaxSum4(int a[],int n)
{
int sum=a[0];
int result=a[0];
for(int i=1;i<n;i++)
{
sum=max(a[i],a[i]+sum);
result=max(sum,result);
}
return result;
}
//方法4的变形!!!!!!!!!!!
int MaxSum44(int a[],int n)
{
int sum=a[n-1];
int result=a[n-1];
for(int i=n-2;i>=0;i--)
{
//sum < 0时,会重置sum的值,这时相当于是一个新的子数组的起点
if(sum<0)
sum=0;
sum += a[i];
//sum > result时,更新result的值,
if(sum > result)
result=sum;
}
return result;
}
//扩展:返回最大子数组的位置
//在sum < 0时,会重置sum的值,相当于这时是一个新的子数组的起点
//在sum > result时,更新result的值,这时我们更新最大子数组的位置
int MaxSum5(int a[], int n, int *start, int *end)
{
//sum :以当前元素开始的和最大的一段子数组
//result:存储当前所有子数组的最大值
//CurStart:存储以当前元素开始的和最大的一段子数组的开始位置
int sum=a[n-1];
int result=a[n-1];
*start=n-1;
*end=n-1;
int CurStart=n-1;
//从数组末尾往前遍历,直到数组首
for(int i=n-2;i>=0;i--)
{
if(sum < 0)
{
sum=0;
//重新标记最大和的起始位置!!!
CurStart=i;
}
sum += a[i];
if(sum > result)
{
result=sum;
//更新最大和的范围!!!
*start=i;
*end=CurStart;//从后往前遍历的原因
}
}
return result;
}
int main()
{
int a1[6]={1,-2,3,5,-3,2};
int a2[6]={0,-2,3,5,-1,2};
int a3[5]={-9,-2,-3,-5,-3};
cout<<MaxSum1(a1,6)<<endl;
cout<<MaxSum1(a2,6)<<endl;
cout<<MaxSum1(a3,5)<<endl;
cout<<MaxSum2(a1,0,5)<<endl;
cout<<MaxSum2(a2,0,5)<<endl;
cout<<MaxSum2(a3,0,4)<<endl;
cout<<MaxSum3(a1,6)<<endl;
cout<<MaxSum3(a2,6)<<endl;
cout<<MaxSum3(a3,5)<<endl;
cout<<MaxSum4(a1,6)<<endl;
cout<<MaxSum4(a2,6)<<endl;
cout<<MaxSum4(a3,5)<<endl;
cout<<MaxSum44(a1,6)<<endl;
cout<<MaxSum44(a2,6)<<endl;
cout<<MaxSum44(a3,5)<<endl;
cout<<"**********返回最大子数组的位置**********"<<endl;
int start=0,end=0;
cout<<MaxSum5(a1,6,&start,&end)<<endl;
cout<<"start: "<<start<<" end: "<<end<<endl;
cout<<MaxSum5(a2,6,&start,&end)<<endl;
cout<<"start: "<<start<<" end: "<<end<<endl;
cout<<MaxSum5(a3,5,&start,&end)<<endl;
cout<<"start: "<<start<<" end: "<<end<<endl;
return 0;
}
代码执行结果:
8
9
-2
8
9
-2
8
9
-2
8
9
-2
8
9
-2
**********返回最大子数组的位置**********
8
start: 2 end: 3
9
start: 2 end: 5
-2
start: 1 end: 1
扩展问题:要求返回最大子数组和的位置,如何处理?
见上述代码及分析