题目
给出一个整数序列S,其中有N个数,定义其中一个非空连续子序列T中所有数的和为T的“序列和”。 对于S的所有非空连续子序列T,求最大的序列和。 变量条件:N为正整数,N≤1000000,结果序列和在范围(-263,263-1)以内。
输入描述:
第一行为一个正整数N,第二行为N个整数,表示序列中的数。
输出描述:
输入可能包括多组数据,对于每一组输入数据,
仅输出一个数,表示最大序列和。
示例1
输入
5
1 5 -3 2 4
6
1 -2 3 4 -10 6
4
-3 -1 -2 -5
输出
9
7
-1
分析
例如给定一个序列A1A2…An 例如1,-2,3,4,-10,6。目标是找到一个连续的子序列Ai到Aj使得Ai+…+Aj最大。那么该例子的最大连续子序列就是3,4,它的和为7。
如果直接来考察这个问题是非常难求解的,这是因为连续子序列的两端都是变化的。所以我们自然而然可以想到,将一端固定下来。例如我们可以考虑某一个元素Aj为末尾元素的情况下,其最大连续子序列的和。表示为…+Aj。那么…+Aj就有两种情况,一种是最大连续子序列只有一个元素Aj,例如上面例子的1,3,6。那么另外一种情况就是最大的连续子序列有多个元素即…+Aj-1+Aj。假设将以Aj为末尾元素的最大连续子序列和为F(j)。那么对于上面两种情况就可以写成F(j) = Aj或者F(j) = F(j-1) + Aj。所以得到F(j) = max{Aj , F(j-1)+Aj}。要么我们要求解该问题,只需依次的从小到大获取所有的F(j),然后从中找到最大的即为最大连续子序列和。
朴素的递归策略(未用到动态规划)
#include <iostream>
#include <algorithm>
#include <climits>
#include <cstdio>
using namespace std;
const int MAXN = 1e6 + 10;
const int INF = INT_MAX;
long long arr[MAXN];
//func(n)为以An为末尾元素的最大连续子序列和
long long func(int n){
long long answer;
if(n == 0){ //以A0为末尾元素,显然最大连续子序列就是A0
answer = arr[0];
}
else{
answer = max(arr[n],func(n-1)+arr[n]);
}
return answer;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;++i){
scanf("%lld",&arr[i]);
}
long long maxNum = -INF;
for(int i=0;i<n;i++){
maxNum = max(maxNum,func(i));
}
printf("%lld\n",maxNum);
}
return 0;
}
这样用递归写虽然可以得到正确答案,但是一般都会超时,因为它的复杂度是n的平方的。而之所以会这么高,是因为在递归中产生了很多相同的子问题,而每次遇到这种子问题我们都要重新计算一次,造成时间浪费,所以复杂度高。
改进:自顶向下的备忘录法
有了上面的分析,我们自然而然可以想到建立一个备忘录数组,将子问题的解存到备忘录里。如果下次又需要求解这个子问题的值,就可以在常数的时间内从备忘录里将这个子问题的值给取出来,而不是重新计算,这就大大的减少了时间复杂度。这就是递归策略+记忆化。其实,动态规划可以理解为加上了备忘录的递归算法。为什么是自顶向下呢?比如F(n) = max{Aj,F(n-1)+Aj},我们要求F(n),需要求出F(n-1)。要求F(n-1),需要求F(n-2)…以此类推,这不就是自顶向下吗。
#include <iostream>
#include <algorithm>
#include <climits>
#include <cstdio>
using namespace std;
const int MAXN = 1e6 + 10;
const int INF = INT_MAX;
long long arr[MAXN];
//func(n)为以An为末尾元素的最大连续子序列和
long long func(int n){
long long answer;
if(n == 0){ //以A0为末尾元素,显然最大连续子序列就是A0
answer = arr[0];
}
else{
answer = max(arr[n],func(n-1)+arr[n]);
}
return answer;
}
long long memo[MAXN]; //备忘录
long long func1(int n){
if(memo[n] != -1){ //表明f(n)已经在备忘录里,就可以直接取出
return memo[n];
}
long long answer;
if(n == 0){ //以A0为末尾元素,显然最大连续子序列就是A0
answer = arr[n];
}
else{
answer = max(arr[n],func1(n-1)+arr[n]);
}
memo[n] = answer; //将F(n)放入备忘录
return answer;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;++i){
scanf("%lld",&arr[i]);
}
// long long maxNum = -INF;
// for(int i=0;i<n;i++){
// maxNum = max(maxNum,func(i));
// }
// printf("%lld\n",maxNum);
fill(memo,memo+n,-1); //初始化备忘录所有元素为-1
long long maxNum = -INF;
for(int i=0;i<n;i++){
maxNum = max(maxNum,func1(i));
}
printf("%lld\n",maxNum);
}
return 0;
}
这就相当于F(0)到F(n-1)每个都只计算了一次,不存在重复计算,那么时间复杂度就降低到了O(n)
自底向上的递推方法
既然可以用递归的方法解决,那么就可以转化为递推的方法解决。
#include <iostream>
#include <algorithm>
#include <climits>
#include <cstdio>
using namespace std;
const int MAXN = 1e6 + 10;
const int INF = INT_MAX;
long long arr[MAXN];
//func(n)为以An为末尾元素的最大连续子序列和
//朴素递归
long long func(int n){
long long answer;
if(n == 0){ //以A0为末尾元素,显然最大连续子序列就是A0
answer = arr[0];
}
else{
answer = max(arr[n],func(n-1)+arr[n]);
}
return answer;
}
//自顶向下备忘录法
long long memo[MAXN]; //备忘录
long long func1(int n){
if(memo[n] != -1){ //表明f(n)已经在备忘录里,就可以直接取出
return memo[n];
}
long long answer;
if(n == 0){ //以A0为末尾元素,显然最大连续子序列就是A0
answer = arr[n];
}
else{
answer = max(arr[n],func1(n-1)+arr[n]);
}
memo[n] = answer; //将F(n)放入备忘录
return answer;
}
//自底向上的递推
long long dp[MAXN]; //记录F(i)的值
void func3(int n){
long long answer;
for(int i=0;i<n;i++){
if(i == 0){
answer = arr[i];
}
else{
answer = max(arr[i],dp[i-1] + arr[i]);
}
dp[i] = answer;
}
return;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;++i){
scanf("%lld",&arr[i]);
}
// long long maxNum = -INF;
// for(int i=0;i<n;i++){
// maxNum = max(maxNum,func(i));
// }
// printf("%lld\n",maxNum);
// fill(memo,memo+n,-1); //初始化备忘录所有元素为-1
// long long maxNum = -INF;
// for(int i=0;i<n;i++){
// maxNum = max(maxNum,func1(i));
// }
// printf("%lld\n",maxNum);
fill(dp,dp+n,-1); //初始化备忘录所有元素为-1
long long maxNum = -INF;
func3(n);
for(int i=0;i<n;i++){
maxNum = max(maxNum,dp[i]);
}
printf("%lld\n",maxNum);
}
return 0;
}
看完之后,有没有让你更加理解动态规划呢?