题意:
给出一个序列,在这个序列中找出一个子序列使这个子序列的和最大。
分析:
网上有很多关于这个题的解题报告,是O(n)时间复杂度的。毫无疑问的是这个算法的时间复杂度和空间复杂度优化的都是非常好的。首先声明一下,这篇文章主要讨论的是如何运用动态规划的思想来解决这个问题。
先来看一组测试数据 6 -1 5 4 -7,那么这个最大的子段是6 -1 5 4 ,如何找到这个最大的子段呢?因为题目是让我们找到整个序列的最大子段和。所以我们可以尝试着把这个问题转化即:求整个序列(前i个数)的最大子段和转化为求
(1)前i-1个数的最大子段和。
(2)前i-1个数中以第i-1个数结尾的最大子段和+a[i]。
(3) a[i]。
这样就把求前i个数的最大子段和转化为了求上面的三个子问题。
但是我们发现了一个新的问题:原问题和子问题之间不是同构的,原问题和问题(1)是同构的,但是和问题(2)不是同构的,虽然和问题(3)也是不同构的,但是问题(3)是非常容易解决的,解决这个问题只需要O(1)的时间复杂度。这样分析过之后问题并不是不能够解决的,只是解决起来比较复杂,所以这一种实现的方法就不再这里写了。
再来看看第(2)个子问题,我们如果把问题转化为求前i个数中以第i个数结尾的最大子段和,那么这样的一个问题就转化为求:
(1)前i-1个数中以第i-1个数结尾的最大子段和+a[i]。
(2)a[i]。
有上面的分析可知,原问题和子问题是同构的。
由此我们可以定义dp[i]是表示以i结尾的最大子段和则状态转移方程为:dp[i] = max(dp[i-1]+a[i], a[i]);
下面是代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn = 100000+100;
int a[maxn], dp[maxn];
int s[maxn], t[maxn];
//dp[i]表示以第i个数结尾的最大子段和,dp[i] = max(a[i]+dp[i-1],a[i]);
int main()
{
int T = 0, n = 0;
scanf("%d",&T);
int count = 0;
while(T--)
{
scanf("%d",&n);
for(int i = 1; i <= n; i++)
{
scanf("%d",&a[i]);
}
dp[0] = 0;
s[0] = t[0] = 1;
for(int i = 1; i <= n; i++)
{
t[i] = i;
if(a[i]+dp[i-1] >= a[i])
{
dp[i] = a[i]+dp[i-1];
s[i] = s[i-1];
}
else
{
dp[i] = a[i];
s[i] = i;
}
}
int ans = 0, ma = -100000;
for(int i = 1; i <= n; i++)
{
if(dp[i] > ma)
{
ma = dp[i];
ans = i;
}
}
printf("Case %d:\n%d %d %d\n",++count,dp[ans],s[ans],t[ans]);
if(T)printf("\n");
}
return 0;
}