动态规划:索引i和j最小的最大子列和问题

题目描述

PAT题目链接

Given a sequence of K integers { N1, N2, ..., NK }. A continuous subsequence is defined to be { Ni, Ni+1, ..., Nj } where 1≤ijK. 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 }, its maximum subsequence is { 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). The second line contains 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 and j (as shown by the sample case). If all the 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

即给出一个长度为k的序列a[k],输出其最大子序和及其对应的子列的首尾元素。如果序列全为负数,则最大子列和输出为0,并输出序列的首尾两个元素。

而我们知道,一个序列最大子序列可能不唯一,而题目中需要我们输出索引i与j最小的那一个最大子序列和及其首尾元素。

动态规划

最初的想法

按照常规动态规划的思想,我们可能按如下方式定义dp数组:

dp[i]表示数组中下标0到i的最大子列和

那么最终的问题就变成了求解dp[k-1]。

而现在的问题是我们如何得到状态转移方程呢?

按照此时对dp数组的定义, 我刚开始得到如下的状态转移方程:

dp[i]=max{dp[i-1],a[i],dp[i-1]+a[i]}

在leetcode上运行时只是部分通过,后面才发现当情况如下图时,状态转移方程不正确:

3

4

5

-1

3

...

...

...

dp[3]=12; a[4]=3; dp[3]+a[4]=15

然而我们发现该序列的最大子列和应为14,也就是说,该状态转移方程不正确,原因在于子列一定是连续的,而我们最初对dp数组的定义无法保证得到的dp[i-1]对应的最大子列的最后一个元素与a[i]相邻。

正解

我们可定义dp数组为:

以a[i]为结尾的最大子列和为dp[i]

这样,相对应的子列,一定包含元素a[i],则dp[i]可以通过两种方式得到

  1. dp[i-1]+a[i]

  1. a[i]

只需取其中的最大者即可,我们便得到了状态转移方程:

dp[i]=max(dp[i-1]+a[i],a[i])

初始化dp数组,dp[0]=a[0](前面没有元素)

此时我们已经基本上可以写出相应代码了,需要注意的是,最终问题的解并非是dp[k-1],而是dp数组中的最大值。

x

y

...

z

a[0]

a[1]

...

a[k-1]

每一个元素a[i]都有对应的dp[i]表示以这个元素为尾部元素的最大子列和,遍历dp数组,找出最大值,则可得到整个序列的最大子列和。

我们用变量l 和r分别表示最大子列的首尾元素在a[k]中的索引,求r的代码如下:


int max=dp[0];
        for (int i = 1; i < k; i++) {
            if (dp[i] > max) { max = dp[i]; r = i; }
        }

只有当dp[i]>max的时候才更新r,确保得到的索引r是最小的。

对于变量l的求解,我们不妨用sum保存最大子列和,用sum不断减去当前a[i]的值,当sum=0时,我们才更新l的值,并接着下一次循环。

注意,这里不能用break跳出循环,否则得到的i将是最大子列和为max时的最大左索引i。


int sum = max;
        for (int i = r; i >= 0; i--) {
            sum -= a[i];
            if (sum ==0) { l = i ; continue; }
        }

代码


#include <iostream>
using namespace std;
int main()
{
    int k = 0;
    cin >> k;
    int* a = new int[k];
    int *dp= new int[k];
    int neg_cnt = 0;
    for (int i = 0; i < k; i++) {
        cin >> a[i];
        if (a[i] <0) neg_cnt++;
    }
    //所给序列全为负数,输出0及其首尾元素值
    if (neg_cnt == k) {
        cout << 0 << " " << a[0] << " " << a[k - 1];
        return 0;
    }

        int l = 0, r = k-1;
        dp[0] = a[0];
        for (auto i = 1; i < k; i++) {
            if (dp[i - 1] + a[i] > a[i]) {
                dp[i] = dp[i - 1] + a[i];
            }
            else {
                dp[i]= a[i];
            }
        }
        int max=dp[0];
        for (int i = 1; i < k; i++) {
            if (dp[i] > max) { max = dp[i]; r = i; }
        }
        int sum = max;
        for (int i = r; i >= 0; i--) {
            sum -= a[i];
            if (sum==0) { l = i;continue; }
        }
        cout << max << " " << a[l] << " " << a[r];
    
}

感想

刚开始求解左索引l时,if条件满足后用的break跳出循环,导致测试点6一直无法通过。

后面再次读题才明白要求最小左索引i,改成continue就可以通过了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值