详解复杂度-Maximum Subsequence Sum

  • 题目来源:
    中国大学MOOC-陈越、何钦铭-数据结构

  • 题目详情:
    给定K个整数组成的序列 [ N ​ 1 , N 2 , . . . , ​ N K ] [N​1, N2, ..., ​NK] [N1,N2,...,NK],“连续子列”被定义为 [ N ​ i , N ​ i + 1 ​ , . . . , N ​ j ] [N​i, N​i+1​ , ..., N​j] [Ni,Ni+1,...,Nj],其中 1 ≤ i ≤ j ≤ K 1≤i≤j≤K 1ijK。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列 [ − 2 , 11 , − 4 , 13 , − 5 , − 2 ] [-2, 11, -4, 13, -5, -2 ] [2,11,4,13,5,2],其连续子列 [ 11 , − 4 , 13 ] [ 11, -4, 13 ] [11,4,13]有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和以及子列的第一个数最后一个数

输入格式:
输入第1行给出正整数 K ( ≤ 100000 ) K (≤100000) K(100000);第2行给出 K K K个整数,其间以空格分隔。

输出格式:
对于每个测试用例,第一行输出最大和,以及最大子序列的第一个和最后一个数字。 数字必须用一个空格分隔,但行尾不能有多余的空格。 如果最大子序列不是唯一的,则输出索引i和j最小的子序列(如示例所示)。 如果所有K个数字均为负,则其最大和定义为0,并且应该输出整个序列的第一个和最后一个数字。

输入样例:

10
-10 1 2 3 4 -5 -23 3 7 -21

输出样例:

10 1 4

解题方法:动态规划
本题可以看作是最大子列和问题的升级版,上一个问题我介绍的动态规划的解法。本题解法与上题类似,不同之处为需要输出子序列起始位的值和终止位的值,因此需要在遍历数组的时候记录相应的起始下标和终止下标。具体分析如下:

  • 状态定义:
    设动态规划列表 d p dp dp ,输入数组 n u m s nums nums d p [ i ] dp[i] dp[i] 代表以输入数组元素 n u m s [ i ] nums[i] nums[i]为结尾的连续子数组最大和。
  • 状态转移方程:
    d p [ i − 1 ] ≤ 0 dp[i−1]≤0 dp[i1]0,说明 d p [ i − 1 ] dp[i - 1] dp[i1] d p [ i ] dp[i] dp[i]产生负的贡献,即
    d p [ i − 1 ] + n u m s [ i ] < n u m s [ i ] dp[i-1] + nums[i]<nums[i] dp[i1]+nums[i]<nums[i]
    因此可得状态转移方程如下:
    d p [ i ] = { d p [ i − 1 ] + n u m s [ i ] dp[i-1]>0 n u m s [ i ] dp[i-1]<=0 dp[i] = \begin{cases} dp[i-1]+nums[i]& \text{dp[i-1]>0}\\ nums[i]& \text{dp[i-1]<=0} \end{cases} dp[i]={dp[i1]+nums[i]nums[i]dp[i-1]>0dp[i-1]<=0
    到此为止上篇文章介绍到的相同,接下来介绍不同的地方。
    不同之处为需要记录下标,先假定初始化为一个很小的数作为最终解 r e s res res,最长子列的起始位下标为 l e f t left left,终止为下标为 r i g h t right right d p [ i ] < 0 dp[i]<0 dp[i]<0时,暂不考虑全负序列,则最大子列的起始位必定从下标 i + 1 i+1 i+1开始,因此维护一个变量 l e f t left left _ t e m p = i + 1 temp = i+1 temp=i+1然后还需判断 r e s res res d p [ i ] dp[i] dp[i]的大小关系,若 r e s res res小则赋值,然后再次判断 d p [ i ] dp[i] dp[i]的大小,如果此时 d p [ i ] < 0 dp[i]<0 dp[i]<0仍满足,则说明序列的前i项均为负值,因此更新 l e f t left left_ t e m p = i temp = i temp=i,再对 l e f t left left r i g h t right right进行赋值。

完整代码如下:

#include <stdio.h>
#include <stdlib.h>

#define max(x, y) (x >= y ? x : y) //定义的宏,用来取最大值
void maxSubSum(int nums[], int n);

int main(){
    int n;
    scanf("%d", &n);
    int *nums = (int*)malloc(sizeof(int)*n);
    if(n)
        for(int i=0; i<n; i++) scanf("%d", &(nums[i]));
    maxSubSum(nums, n);

}
做法一 只使用动态规划的思想,维护一个临时变量
//void maxSubSum(int nums[], int n){
//    int res = -1e3, temp = 0;
//    int left_temp = 0, left = 0, right = 0;
//    for(int i=0; i<n; i++){
//        temp += nums[i];
//        if(temp<0){
//            temp = 0;
//            left_temp = i+1;
//        }
//        if(res<temp){
//            res = temp;
//            left = left_temp;
//            right = i;
//        }
//    }
//    if(res >= 0) printf("%d %d %d\n", res, nums[left], nums[right]);
//    else printf("%d %d %d\n", 0, nums[0], nums[n-1]);
//}

//做法二 动态规划
void maxSubSum(int nums[], int n){
    int res = -1e3; //目标值初始化
    int left_temp = 0, left = 0, right = 0;
    int *dp = (int*)malloc(sizeof(int)*n);
    for(int i=0; i<n; i++){ //动态规划遍历数组,求最大值
        if(i==0){
            dp[i] = nums[i];
        }
        else
            dp[i] = max(dp[i-1], 0) + nums[i];
        if(dp[i]<0){ //dp[i]起到负贡献,记录序列开始下标为i+1
            left_temp = i + 1;
        }
        if(res < dp[i]){
            res = dp[i];
            if(dp[i]<0) //说明前i位皆为负数,因此下标为i
                left = left_temp - 1;
            else left = left_temp;
            right = i;
        }
    }
    if(res >= 0) printf("%d %d %d\n", res, nums[left], nums[right]);
    else printf("%d %d %d\n", 0, nums[0], nums[n-1]);

}

在链接中的文章的最后我分析了解法一和解法二动态规划的区别,本次将两种解法的代码都给出。因为本题对序列全负的处理同上一题:如果所有K个数字均为负,则其最大和定义为0。 因此动态规划的普适性也优于解法一,如果没有括号中的条件要求(如果所有K个数字均为负,则其最大和定义为0。 ),动态规划的程序也可以实现,只需将最后的输出部分改为以下即可。

printf("%d %d %d\n", res, nums[left], nums[right]);

如果有哪里理解错的地方欢迎大家留言交流。

手工码字码代码,如果有帮助到你的话留个赞吧,谢谢。

以上。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值