最大子列和
题目:给定N个整数的序列{ A 1 A_1 A1, A 2 A_2 A2, A 3 A_3 A3… A n A_n An},求函数f(i,j)=max{0, ∑ i j A n \sum_i^j\ A_n ∑ij An}的最大值。
即找出序列间连续的数字之和最大值。
1.三重循环
最外层循环定义左边界i,次外层循环定义右边界j,而最内层循环则将[i,j]区间内的值相加再与最大子列和比较,若大于则赋值给最大子列和。
int MaxSubeseqSum1(int A[],int n)
{
int thisSum = 0,maxSum = 0;
for(int i = 0; i<n; i++){
for(int j = i;j<n;j++){
thisSum = 0;
for(int k = i; k<=j; k++){
thisSum +=A[k];
}
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
时间复杂度为O( n 3 n^3 n3)
办法太过于暴力,有没有更好的办法呢?答案是有的。我们知道其实在最内层循环的时候不必每次都从最左值加到最右值,只需要将上一次的和在往右边加一项即可,也就是说k这趟最内层的循环可以省掉。
2.二重循环
我们只需要改一改上面的代码,将k的最内层循环省掉,计算和放在j的次外层循环。
int MaxSubseqSum2(int A[],int n)
{
int thisSum = 0,maxSum = 0;
for(int i =0; i<n;i++){
thisSum = 0;
for(int j = i;j<n;j++){
thisSum +=A[j];
}
if(thisSum > maxSum)
maxSum = thisSum;
}
return maxSum;
}
时间复杂度为O( n 2 n^2 n2)
写出以上的代码已经有很大的进步,但是 n 2 n^2 n2同样是不是一个很好的算法,我们还可以采用分治的思想更进一步优化算法。
3.分治
分治,分而治之,即把大问题化成小问题。
我们来借助一组数据来说明这个问题:{6,2,-1,7,5,-4,3,2}
我们利用递归将数组不断切成两半,我们挑左半部分的前四步来看
初始化最大子列和为0,先将6对比最大子列和比较发现6>最大子列和,将6赋值给最大子列和,再将6+2与最大子列和比较(6<6+2=8)则将8赋值给最大子列和,然后-1和7也如此。最关键的是在6、2、-1、7之中中间界限也要加入比较即2+(-1)也要比较。直接贴代码理解吧。
//左扫描
MaxLeftBorderSum = 0,LeftBorderSum = 0;
for(i=center;i>=left;i--){
LeftBorderSum += A[i];
if(LeftBorderSum>MaxLeftBorderSum)
MaxLeftBorderSum = LeftBorderSum;
}
//右扫描
MaxRightBorderSum = 0, RightBorderSum =0;
for(i=center+1;i<=right;i++){
RightBorderSum += A[i];
if(RightBorderSum > MaxRightBorderSum)
MaxRightBorderSum = RightBorderSum;
if(RightBorderSum>MaxRightBorderSum)
MaxRightBorderSum = RightBorderSum;
}
return Max(MaxLeftSum,MaxRightSum,MaxLeftBorderSum + MaxRightBorderSum );
}
int MaxSubseqSum3(int A[],int n)
{
return Div(A,0,n-1);
}
时间复杂度为O( n l o g n nlog_n nlogn)
使用分治我们将算法优化到了O( n l o g n nlog_n nlogn),那有没有时间复杂度为O( n n n)的算法呢,即扫描一次即可得到最大子列和的算法,答案同样是有的。
4.在线处理
我们在解决这个问题的时候,最关键的是找到连续的最大数值之和,注意是连续,所以我们可以定义一个当前数值之和与最大子列和相比较,当当前子列和小于0时我么知道前面连续的和都为不可取,所以我们将前面的子列和都扔掉(赋值0),再进行下面的计算。
int MaxSubseqSum4(int A[],int n)
{
int maxSum,thisSum,i;
maxSum = 0,thisSum = 0;
for( i=0; i<n; i++){
thisSum += A[i];
if(thisSum > maxSum){
maxSum = thisSum;
}
else if(thisSum<0){
thisSum = 0;
}
}
return maxSum;
}
时间复杂度为O(
n
n
n)
这应该已经是最好的算法了,因为解决这个问题至少要扫描一趟这个数组。
完整代码
#include<stdio.h>
int MaxSubeseqSum1(int A[],int n)
{
int thisSum = 0,maxSum = 0;
for(int i = 0; i<n; i++){
for(int j = i;j<n;j++){
thisSum = 0;
for(int k = i; k<=j; k++){
thisSum +=A[k];
}
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
int MaxSubseqSum2(int A[],int n)
{
int thisSum = 0,maxSum = 0;
for(int i =0; i<n;i++){
thisSum = 0;
for(int j = i;j<n;j++){
thisSum +=A[j];
}
if(thisSum > maxSum)
maxSum = thisSum;
}
return maxSum;
}
//分而治之
int Max(int a,int b,int c)
{
if(a<b)
a = b;
if(a<c)
a = c;
return a;
}
int Div(int A[],int left,int right)
{
int MaxLeftSum,MaxRightSum;
int MaxLeftBorderSum, MaxRightBorderSum;
int LeftBorderSum,RightBorderSum;
int center,i;
if(left == right){
if(A[left]>0) return A[left];
else return 0;
}
center = (left + right)/2;
MaxLeftSum = Div(A,left,center);
MaxRightSum = Div(A,center+1,right);
//左扫描
MaxLeftBorderSum = 0,LeftBorderSum = 0;
for(i=center;i>=left;i--){
LeftBorderSum += A[i];
if(LeftBorderSum>MaxLeftBorderSum)
MaxLeftBorderSum = LeftBorderSum;
}
//右扫描
MaxRightBorderSum = 0, RightBorderSum =0;
for(i=center+1;i<=right;i++){
RightBorderSum += A[i];
if(RightBorderSum > MaxRightBorderSum)
MaxRightBorderSum = RightBorderSum;
if(RightBorderSum>MaxRightBorderSum)
MaxRightBorderSum = RightBorderSum;
}
return Max(MaxLeftSum,MaxRightSum,MaxLeftBorderSum + MaxRightBorderSum );
}
int MaxSubseqSum3(int A[],int n)
{
return Div(A,0,n-1);
}
//在线处理
int MaxSubseqSum4(int A[],int n)
{
int maxSum,thisSum,i;
maxSum = 0,thisSum = 0;
for( i=0; i<n; i++){
thisSum += A[i];
if(thisSum > maxSum){
maxSum = thisSum;
}
else if(thisSum<0){
thisSum = 0;
}
}
return maxSum;
}
int main()
{
int n;
scanf("%d",&n);
int A[n];
for(int i = 0;i<n;i++)
scanf("%d",&A[i]);
printf("%d\n",MaxSubeseqSum1(A,n));
printf("%d\n",MaxSubseqSum2(A,n));
printf("%d\n",MaxSubseqSum3(A,n));
printf("%d\n",MaxSubseqSum4(A,n));
return 0;
}
还在学习,还在成长,如有错误望能指正。