最大子列和算法

来源:https://pintia.cn/problem-sets/16/problems/663,Maximum Subsequence Sum 2004年浙江大学计算机专业考研复试真题

原文

Maximum Subsequence Sum

Given a sequence of K K K integers { N 1 , N 2 , ⋯   , N K ​ ​ } \{N_1, N_2, \cdots, N_K ​​\} {N1,N2,,NK}. A continuous subsequence is defined to be { N i , N i + 1 ​ ​ , ⋯   , N ​ j ​ ​ } \{N_i, N_{i + 1} ​​, \cdots, N_​j ​​\} {Ni,Ni+1,,Nj} where 1 ≤ i ≤ j ≤ K 1≤i≤j≤K 1ijK. The Maximum Subsequence is the continuous subsequence which has the largest sum of its elements. For example, given sequence { − 2 , 11 , − 4 , 13 , − 5 , − 2 } \{-2, 11, -4, 13, -5, -2\} {2,11,4,13,5,2}, its maximum subsequence is { 11 , − 4 , 13 } \{11, -4, 13\} {11,4,13} with the largest sum being 20.
Now you are supposed to find the largest sum, together with the first and the last numbers of the maximum subsequence.

Input Specification

Each input file contains one test case. Each case occupies two lines. The first line contains a positive integer K ( ≤ 10000 ) K (≤10000) K(10000). The second line contains K K K numbers, separated by a space.

Output Specification

For each test case, output in one line the largest sum, together with the first and the last numbers of the maximum subsequence. The numbers must be separated by one space, but there must be no extra space at the end of a line. In case that the maximum subsequence is not unique, output the one with the smallest indices i i i and j j j (as shown by the sample case). If all the K K K numbers are negative, then its maximum sum is defined to be 0, and you are supposed to output the first and the last numbers of the whole sequence.

Sample Input

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

Sample Output

10 1 4

解题

题目的意思大概是求一个序列的最大子列和。
如: { − 10 , 1 , 2 , 3 , 4 , − 5 , − 23 , 3 , 7 , − 21 } \{-10,1,2,3,4,-5,-23,3,7,-21\} {10,1,2,3,4,5,23,3,7,21}的最大子列和是10,并且子列和第一个数字是1,最后一个数字是4,所以输出 10 1 4
特殊情况:

  1. 全是负数输出0
  2. 存在多个最大子序列时,输出一个子序列的头和尾

最大子列和算法

本题是基于最大子列和算法,只不过在此基础上要求输出头和尾。
使用在线处理,最大子列和算法:

int getMaxSubseqSum(int A[], int N) {
    int ThisSum, MaxSum;
    int i;
    ThisSum = MaxSum = 0;
    for (i = 0; i < N; i++) {
        ThisSum += A[i]; //向右累加
        if (ThisSum > MaxSum)
            MaxSum = ThisSum; //发现更大和则更新当前结果
        else if (ThisSum < 0) //当前子列和为负
            ThisSum = 0;      //则不能使后面的部分和增大,抛弃之
    }
    return MaxSum;
}

这个算法的时间复杂度是 O ( N ) O(N) O(N)

难点

寻找最大子列和本身不难。
我们发现若要记录最大子列的右边的索引会很简单,只需要在 if(ThisSum>MaxSum) 判断成功后让当前的i更新为右边的索引即可。
而寻找左边索引是本题的关键。

设置fristlastfirstIDlastID用于存储索引值和索引。

思路一

参考自:https://blog.csdn.net/qq_40644568/article/details/90140420

在在线处理的基础上原地更改,仅当thisSum >= maxSum时更新firstlast,而thisSum < 0时只更新firstIDlastID
这样做,前提是a[]必须要在在线处理这个循环前,完成初始化,因为当a[]全为负数时,永远不会进入thisSum >= maxSum的循环,那么一开始就要声明 first = a[0]; last = a[n - 1];
但这会消耗一部分时间,用于专门输入数组。

#include <iostream>
using namespace std;
int a[100005];

int main() {
    auto thisSum{0}, maxSum{0},   // 将 当前子序列和 以及 目前最大子序列和 分别初始化为0
            firstID{0}, lastID{0},// 标识左右位置
            first{0}, last{0},
            n{0};
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    first = a[0];
    last = a[n - 1];
    for (int i = 0; i < n; i++) {
        thisSum += a[i];        // 向右累加
        if (thisSum >= maxSum) {// 因为取指数i和j最小的那串序列,所以在多个子序列相同时,
                                // 取第一次得到的序列。但这里会排除掉遇到0的情况,所以还需要加一个if语句(见↓)
            if (thisSum > maxSum) {
                maxSum = thisSum;
                lastID = i;
                first = a[firstID];
                last = a[lastID];
            }
            else if (thisSum == 0) {//(接上一条注释)在只有0和负数组成的序列中,第一次遇到的0就是所求序列
                maxSum = thisSum;
                lastID = i;
                first = a[firstID];
                last = a[lastID];
            }
        } else if (thisSum < 0) {// 如果当前子列和为负
            thisSum = 0;         // 则不可能使后面的部分和增大,抛弃之
            firstID = i + 1;
            lastID = firstID;
        }
    }
    printf("%d %d %d\n", maxSum, first, last);
    return 0;
}

思路二

参考自:https://blog.csdn.net/tiao_god/article/details/105001659

思路二,逆向思维,从左往右遍历,找到的是最大子列的右边索引,我们从右往左遍历,不就能找到最大子列的左索引了吗?

#include <iostream>
using namespace std;
int a[100005];

int main() {
    auto thisSum{0},              // 将 当前子序列和初始化为0
            maxSum{-1},           // 将 目前最大子序列和 初始化为-1,
                                  // 目的是如果全是负数,则maxSum将一直是-1,可以最后检测出
            firstID{0}, lastID{0},// 标识左右位置
            n{0};
    scanf("%d", &n);
    lastID = n - 1;
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
        thisSum += a[i];// 向右累加
        if (thisSum > maxSum) {
            maxSum = thisSum;
            lastID = i;
        } else if (thisSum < 0) {
            thisSum = 0;
        }
    }
    thisSum = 0;
    maxSum = -1;
    for (int i = lastID; i >= 0; i--) {
        thisSum += a[i];// 向左累加
        if (thisSum >= maxSum) {
            maxSum = thisSum;
            firstID = i;
        } else if (thisSum < 0) {
            thisSum = 0;
        }
    }
    if (maxSum < 0) {
        maxSum = 0;
        firstID = 0;
        lastID = n - 1;
    }
    printf("%d %d %d\n", maxSum, a[firstID], a[lastID]);
    return 0;
}

两种思路实际比较

思路一较耗时,因为有用于专门输入数组的时间,这大概率大于思路二中的向左累加的时间

PTA测试注意

PTA上此题的测试点,有一些需要在代码中反复斟酌:

  1. sample换1个数字。有正负,负数开头结尾,有并列最大和
    需考虑只找出第一个最大子列和的索引序号
  2. 最大和序列中有负数
  3. 并列和对应相同i但是不同j,即尾是0
  4. 1个正数
  5. 全是负数
    需特殊处理
  6. 负数和0
    需特殊处理
  7. 最大和前面有一段是0
    需特殊处理
  8. 最大N
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值