问题概述
这是一个经典的问题。
给定一个长度为n的序列a[1],a[2]...a[n-1],a[n]
求一个连续的子序列 a[i],a[i+1]...a[j-1],a[j],使得a[i]+a[i+1]...a[j-1]+a[j]最大。
暴力的方法就是双重循环枚举左右端点,然后直接找最大的就好了。 但是算法的复杂度是 O(N^2) 不够优秀
动态规划解法 复杂度O(n)
我们让 dp[ i ] 代表以 a[ i ] 为结束的最大连续子段和
因为是以a[ i ]为结束且是连续子段 那么
dp[ i ] 要么就是 a[ i ]本身
要么 就是a[ i ] + 以a[ i-1 ]为结束的最大连续字段和 也就是 a[ i ] + dp[ i - 1 ]
所以 状态转移方程出来了 dp[i] = max( A[i], dp[i-1]+A[i] )
int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lld",&a[i]);//输入 dp[0]=0; for(int i=1;i<=n;i++){//状态转移方程 dp[i]=max(a[i],dp[i-1]+a[i]); } ll maxn=0; for(int i=1;i<=n;i++){//遍历找最大值 maxn=max(dp[i],maxn); } printf("%lld ",maxn); }
这个算法的常数还是比较大的,我们可以优化下常数
ll a[maxn]; ll n,ans,dp; int main(){ scanf("%lld",&n); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); dp=max(a[i],dp+a[i]); ans=max(dp,ans); } printf("%lld ",ans); }
如果我们还想知道这个具体的最大连续区间的左右端点是什么
1 int a[maxn]; 2 int main() { 3 //freopen("../in.txt","r",stdin); 4 int t; 5 scanf("%d",&t); 6 int num = 1; 7 while (t--) { 8 int n; 9 scanf("%d",&n); 10 int ans = -INF,dp = 0,l = 1,r = 1,k = 1; 11 for (int i = 1;i <= n;i++) { 12 scanf("%d",&a[i]); 13 if (dp >= 0) { 14 dp += a[i]; 15 } 16 else { 17 dp = a[i]; 18 k = i; 19 } 20 if (dp > ans) { 21 ans = dp; 22 l = k; 23 r = i; 24 } 25 } 26 printf("Case %d: ",num++); 27 printf("%d %d %d ",ans,l,r); 28 if (t != 0) 29 printf(" "); 30 } 31 return 0; 32 }