给定n(1<=n<=100000)个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时定义子段和为0,依此定义,所求的最优值为: Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n。 例如,当(a[1],a[2],a[3],a[4],a[5],a[6])=(-2,11,-4,13,-5,-2)时,最大子段和为20。
注意:本题目要求用动态规划法求解,只需要输出最大子段和的值。
Input
第一行输入整数n(1<=n<=100000),表示整数序列中的数据元素个数;
第二行依次输入n个整数,对应顺序表中存放的每个数据元素值。
Output
输出所求的最大子段和
Sample Input
6 -2 11 -4 13 -5 -2
Sample Output
20
Hint
Source
算法1:算出来每一个字段和的大小,然后进行比较,最后选出最大的那个。
代码如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int a[100001] ; //用于储存数据
int main()
{
int i , j , k ;
int n ;
int sum , maxsum ;
scanf("%d",&n) ;
for( i = 0 ; i < n ; i++)
scanf("%d",&a[i]) ;
maxsum = 0 ;
for( i = 0 ; i < n ; i++ )//i是子列左端的位置
{
for(j = i ; j< n ; j++){//j是子列右端的位置
sum = 0 ; //记录每一次的子列和
for(k = i ; k<=j ; k++)
sum+=a[k] ;
if(sum>maxsum)
maxsum = sum ;
}
}
printf("%d\n",maxsum) ;
return 0 ;
}
这个算法理解起来简单但是时间复杂度比较高是O(N^3),对于这道题来说会超时。
算法2:
第一种算法耗时比较大,在第一种算法的基础上对第一种算法进行改进,改进后的思路:从第一个数开始每加一个便进行一次比较,从第一个数加到最后一个数以后在从第二个数开始,到最后一个数时程序结束。
代码如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int a[100001] ; //用于存储数据
int main()
{
int i , j ;
int n ;
int sum , maxsum ;
maxsum = 0 ;
scanf("%d",&n) ;
for( i = 0 ; i< n ; i++)
scanf("%d",&a[i]) ;
for( i = 0 ; i<n ; i++)
{
sum = 0 ;
for( j = i ; j <n ; j++)
{
sum+= a[j] ;
if( sum > maxsum )
maxsum = sum ;
}
}
printf("%d\n",maxsum) ;
return 0 ;
}
第二类算法相对于第一种算法耗时要比第一种少的多,它的时间复杂度是O(N^2)。然而相对于我们这道题目它的时间复杂度还是十分的大。
第3种算法(分而治之):
大致思路是将数据从中间分开,运用递归的思想分别算中间和分开两端的最大子段和。
代码如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int a[50001] ; //用于存储数据
int max3(int a , int b, int c)//计算3个数的大小,打擂法
{
int max ;
max = a ;
if(max<b)
{
max = b ;
}
if(max<c)
{
max = c ;
}
return max ;
}
int DV(int left , int right)
{
int MaxLsum , MaxRsum ; //存放左右子问题的解
int Lsum , Rsum ; //存放跨分界线的结果
int tLsum , tRsum ; //暂时存放
int mid , i ;
//函数终止条件
if(left== right )
{
if(a[left]>0)
return a[left] ;
else return 0 ;
}
//下面是分的过程
mid = (left+right) / 2 ;
//递归求两边子列的最大和
MaxLsum = DV(left, mid) ;
MaxRsum = DV(mid+1,right ) ;
//下面是求跨分界线的最大子列和
Lsum = 0 ; Rsum = 0 ;
tLsum = 0 ; tRsum = 0 ;
//从中线向右扫描
for(i = mid +1 ; i<= right ; i++)
{
tRsum += a[i] ;
if(tRsum>Rsum)
Rsum = tRsum ;
}
//从中线向左扫描
for(i = mid ; i>=left ; i--)
{
tLsum += a[i] ;
if(tLsum>Lsum)
Lsum = tLsum ;
}
return max3(MaxLsum, MaxRsum,Lsum+Rsum) ;
}
int main()
{
int i ;
int n ;
int max ;
scanf("%d",&n) ;
for(i = 0 ; i < n ;i++)
{
scanf("%d",&a[i]) ;
}
max = DV(0,n-1) ; //分治函数
printf("%d\n",max) ;
return 0 ;
}
该算法的时间复杂度相较于前两个算法来说明显要比前两个算法的小的多,该算法的时间复杂度是O(NlogN),然而对于这道题这个算法仍不是最佳的,接下来看第4种算法。
第4种算法(动态规划):
该算法思路是用a数组来存储数据,首先写出动态规划方程b[i] = b[i-1] +a[i] ;(a[i]+b[i-1]>0)如果a[i]+b[i-1]<=0那么b[i] = 0 ;
代码如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int a[100001] ; //用于储存数据
int b[100001] ;
int main()
{
int i , n ;
int t ;
int max ;
scanf("%d",&n ) ;
for( i = 0 ; i < n ; i++)
scanf("%d",&a[i]) ;
if(a[0] >0)
b[0] = a[0] ;
for( i = 1 ; i< n ; i++)
{
t = b[i-1] + a[i] ;
if(t>0)
b[i] =t ;
else b[i] = 0 ;
}
max = b[0] ;
for(i = 1 ; i< n ; i++)//遍历寻找最大子段和
{
if(b[i]>max)
max = b[i] ;
}
printf("%d\n",max) ;
return 0 ;
}
该算法的时间复杂度是O(N)明显比上面的要快的多,但由于用了两个数组并且数组的范围比较大就会导致空间复杂度较大,对于这道题来说这个算法会导致超出空间下面是对这个算法的改进。
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int a[100001] ;
int main()
{
int max ;
int i , sum;
int n ;
scanf("%d",&n) ;
for( i = 0 ; i < n ; i++)
scanf("%d",&a[i]) ;
sum = max = 0 ;
for( i = 0 ; i < n ; i++)
{
sum += a[i] ;
if(sum>max)
max = sum ;
else if(sum<0)
sum = 0 ;
}
printf("%d\n",max) ;
return 0 ;
}