一、题目描述
Description
给定有n个整数(可能为负整数)组成的序列a1,a2,…,an,求该序列连续的子段和的最大值。 如果该子段的所有元素和是负整数时定义其最大子段和为0。
Input
第一行有一个正整数n(n<1000),后面跟n个整数,绝对值都小于10000。直到文件结束。
Output
输出它的最大子段和.
Sample Input
6 -2 11 -4 13 -5 -2
Sample Output
20
二、解决方法
1.穷举法
int main()
{
int n;
int s=0;
int max=0;
int b[1000];
int sum=0;
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&b[i]);
for(int i=0; i<n; i++)
{
for(int j=i; j<n; j++)
{
sum=0;
for(int k=i; k<=j; k++)
{
sum+=b[k];
}
if(sum>=max)
max=sum;
}
}
printf("%d",max);
return 0;
}
分析:
(1)根据上述代码可知,该种方法的时间复杂度为O(N3)
(2)对于一个给定的数组,变量i用来确定初始位置,j用来确定子段的元素个数,k用来确定i与j之间的数组元素,由i和j共同约束。如下:
2.优化穷举法
以题目中所给例子为例,-2 11 -4 13 -5 -2,利用第一种方法,当i=0时计算的过程是:
j=0,sum=b[0]
j=1,sum=b[0]+b[1]
j=2,sum=b[0]+b[1]+b[2]
j=3,sum=b[0]+b[1]+b[2]+b[3]
由上面的几个式子可以得出一个规律,k<=j的部分元素和在k<=j-1时已被计算,即在k<=j时,部分元素的和被重复计算。也就是说,对于k=i这个循环,可以用非循环方式替换,以减少时间复杂度。
优化代码如下:
int main()
{
int n;
int max=0;
int sum=0;
int b[1000];
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&b[i]);
for(int i=0; i<n; i++)
{
sum=b[i];
if(sum>=max)
max=sum;
for(int j=i+1; j<n; j++)
{
sum=sum+b[j];
if(sum>max)
max=sum;
}
sum=0;
}
printf("%d",max);
return 0;
}
分析:
(1)根据上述代码可知,该程序的时间复杂度为O(N2)
(2)i仍旧确定子段的初始位置,max用来存放当前的子段最大和,sum用来存放当前的子段和。在第二个循环中,依次把数组i+1–>n-1位置的元素加入sum,且每加一次,把sum的值和max进行比较,保证max所存的值是当前已计算过的子段和中最大的值。在进行下一个初始位置之前,sum一定要清零。
3.分治法
1.分治法的基本思想是把规模为n的问题分解成k个规模较小的问题。在本题目中,若使用分治法,可把数组分成三个部分:
先把数组分成两部分,即假设整个数组为n,初始位置为left,结束位置为right,则中间mid=(left+right)/2;
(1)left–>mid之间的最大子段和
(2)mid+1–>right之间最大子段和
(3)还有一种情况是,最大子段和跨越(1)(2)两段,解决办法是以mid为开始,向左和向右分别求出最大子段和,然后相加即为跨越左右两段时的最大子段和。
最后,把情况(3)与前两种(1)(2)进行比较,得出最大的子段和。
2.经计算,分治法的时间复杂度为O(NlogN)
int Max_Sum(int b[],int left,int right)
{
int sum=0;
if(left == right)
return sum=b[left]>0?sum:0;
int mid = (left+right)/2;
int leftsum=Max_Sum(b,left,mid);
int rightsum=Max_Sum(b,mid+1,right);
int s1=0;
int left_sum=0;
for(int i=mid; i>=left; i--)
{
left_sum+=b[i];
if(left_sum>=s1)
s1=left_sum;
}
int s2=0;
int right_sum=0;
for(int i=mid+1; i<=right; i++)
{
right_sum+=b[i];
if(right_sum>=s2)
s2=right_sum;
}
sum=s1+s2;
if(sum<leftsum)
sum=leftsum;
if(sum<rightsum)
sum=rightsum;
return sum;
}
int main()
{
int n;
int max;
int b[10000];
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&b[i]);
max=Max_Sum(b,0,n-1);
printf("%d",max);
return 0;
}
4.动态规划
(1)根据题目要求如果该子段的所有元素和是负整数时定义其最大子段和为0:
令f(i)为由数组元素b[0]–>b[i]的最大子列和,当数组前i-1项和小于0,则f(i)取b[i],当f(i-1)大于0,则f(i)=f(i-1)+b[i],即:
f(i-1)<0 , f(i)=b[i];
f(i-1)>0 , f(i)=f(i-1)+b[i];
(2)经分析,其时间复杂度为O(N)
代码如下:
int Max_Sum(int b[],int n)
{
int max=0;
int sum=0;
for(int i=0; i<n; i++)
{
if(sum<=0)
sum=b[i]; //前i-1项和小于0,舍弃前i-1项和,从第i项开始计数
else
sum+=b[i]; //前i-1项和大于0
if(sum>=max)
max=sum;
}
return max;
}
int main()
{
int n;
int max;
int b[10000];
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&b[i]);
max=Max_Sum(b,n);
printf("%d",max);
return 0;
}