最大连续子序列和问题

目录

题目描述

题目分析

图一乐之超级遍历法

聪明了一些之还是遍历法

递归分治法

动态规划解法

联机算法

写在最后


题目描述

给定一个数字序列A_1,A_2,...,A_n,求i, j (1 ≤ i ≤ j ≤ n),使得A_i+...+A_j最大,输出这个最大和。

题目分析

虽然这篇博客旨在讲解动态规划解法,但我们还是先来看看各种解法都有哪些:

图一乐之超级遍历法

#include <iostream>
using namespace std;
const int N = 1010;
int num[N];
int MaxSubSequenceSum(const int A[], int N){
	int ThisSum, MaxSum = 0;
	for(int i = 0;i < N; i ++){
		for(int j = i;j < N;j ++){
			ThisSum = 0;
			for(int k = i;k <= j;k ++){
				ThisSum += A[k];
			if(ThisSum > MaxSum){
				MaxSum = ThisSum;
			}	
			}
		}
	}
	return MaxSum;
}
int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++)
        cin >> num[i];
    cout << MaxSubSequenceSum(num, n) << endl;
    return 0;
}

最基础的想法,就是穷举所有可能,时间复杂度为O(n^3)

聪明了一些之还是遍历法

第一种方法涉及了太多的重复运算,笨到令人发指的程度。因此我们可以考虑撤掉一个for循环进行优化。

#include <iostream>
using namespace std;
const int N = 1010;
int num[N];
int MaxSubSequenceSum(const int A[], int N){
	int ThisSum, MaxSum = 0;
	for(int i = 0;i < N; i ++){
		ThisSum = 0;
        for(int j = i; j <N; j ++){
            ThisSum += A[j];
            if(ThisSum > MaxSum)
                MaxSum = ThisSum;
        }
	}
	return MaxSum;
}
int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++)
        cin >> num[i];
    cout << MaxSubSequenceSum(num, n) << endl;
    return 0;
}

现在,时间复杂度变成了O(n^2)

递归分治法

在问题中,最大连续子序列无非出现在三个地方:

  1. 全部出现在序列的左半边;
  2. 全部出现在序列的右半边;
  3. 出现在中部,跨越左右半边

对于前两种情况,我们可以采用递归求解的方式,而第三种情况的最大和可以通过求出左半边的最大和(包含左半边的最后一个元素)加上右半边的最大和(包含右半边的第一个元素)而得到。易知分治法的复杂度为O(logN),又因为for循环最多只有一层,所以该算法的复杂度为O(NlogN)

#include <iostream>
using namespace std;
const int N = 1010;
int num[N];
int MaxSubSequenceSum(const int A[], int Left, int Right){
	int MaxLeftSum = 0, MaxRightSum = 0;
    int MaxLeftBorderSum = 0, MaxRightBorderSum = 0;
    int LeftBorderSum = 0, RightBorderSum = 0;
    int Center;
    if(Left == Right){
        if(A[Left] > 0) return A[Left];
        else return 0;
    }
    Center = (Right + Left) / 2;
    MaxLeftSum = MaxSubSequenceSum(A, Left, Center);
    MaxRightSum = MaxSubSequenceSum(A, Center + 1, Right);

    for(int i = Center; i >= Left; i --){
        LeftBorderSum += A[i];
        if(LeftBorderSum > MaxLeftBorderSum)
            MaxLeftBorderSum = LeftBorderSum;
    }
    for(int i = Center + 1; i <= Right; i ++){
        RightBorderSum += A[i];
        if(RightBorderSum > MaxRightBorderSum)
            MaxRightBorderSum = RightBorderSum;
    }
    return max(max(MaxLeftSum, MaxRightSum), MaxRightBorderSum + MaxLeftBorderSum);
}
int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++)
        cin >> num[i];
    cout << MaxSubSequenceSum(num, 0, n - 1) << endl;
    return 0;
} //go to hell

动态规划解法

上面的分治递归求解是不是感觉很高大上,把C++写出了Java的感觉

事实上费力不讨好,还不是被动态规划一棒子撂倒

下面,我们考虑用标准的动态规划来求解。

现进行如下思考:

1.令状态dp[i]表示以A[i]作为末尾的连续序列的最大和,所求的东西就是dp数组中的最大值。

2.做如下考虑:因为dp[i]要求是必须以A[i]结尾的连续序列,只有两种情况:

  • 这个最大和的连续序列只有一个元素,即以A[i]开始,以A[i]结尾;
  • 这个最大和的连续序列有多个元素

于是得到状态转移方程:

dp[i]=max\left \{ A[i],dp[i-1]+A[i] \right \}

这个式子只和i与i之前的元素有关,边界条件是dp[1] = A[1],dp数组的最大值就是答案。

算法的时间复杂度为O(N)

代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010;
int A[N], dp[N];
int main(){
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++) {
        cin >> A[i];
    }
    dp[1] = A[1];
    for(int i = 2; i <= n; i ++) {
        dp[i] = max(A[i], dp[i - 1] + A[i]);
    }
    int ans = 0;
    for(int i = 1; i <= n; i ++) {
        ans = max(dp[i], ans);
    }
    cout << ans << '\n';

    return 0;
}

联机算法

个人认为最简单也最好想,本质上是动态规划的思想。

该算法对所有数据遍历一遍,一旦A[ i ]被读入并被处理,它就不再需要被记忆。由于该问题不需要最大序列和的坐标输出,所以我们不需要记忆序列位置,仅仅记忆当前最大序列和的值即可。

算法的时间复杂度为O(N)

代码如下:

#include <iostream>
using namespace std;
const int N = 1010;
int num[N];

int MaxSubSequenceSum(const int A[ ], int N){
	int ThisSum, MaxSum, j;
 
	ThisSum = MaxSum = 0;
	for (j= 0; j < N; j++) {
		ThisSum += A[j];
		if (ThisSum > MaxSum)
		    MaxSum = ThisSum;
        if(ThisSum < 0)
			ThisSum = 0;
	}
	return MaxSum;
}
int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++)
        cin >> num[i];
    cout << MaxSubSequenceSum(num, n - 1) << endl;
    return 0;
}

写在最后

最后...如果你有兴趣的话,欢迎来挑战这个问题的加强版本!

动态规划之最大两段连续子序列和_AryCra_07的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AryCra_07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值