leetcode-最大连续子序列

leetcode-最大连续子序列

一、题目

给定 K 个整数的序列{ N1, N2, …, NK },其任意连续子序列可表示为{ Ni, Ni+1, …, Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个,例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为 20。现在增加一个要求,即还需要输出该子序列的第一个和最后一个元素。

输入描述:

测试输入包含若干测试用例,每个测试用例占 2 行,第 1 行给出正整数K(K < 10000),第 2 行给出 K 个整数,中间用空格分隔。当 K 为 0 时,输入结束,该用例不被处理。

输出描述:

对每个测试用例,在 1 行里输出最大和、最大连续子序列的第一个和最后一个元素,中间用空格分隔。如果最大连续子序列不唯一,则输出序号 i 和 j 最小的那个(如输入样例的第 2、3 组)。若所有 K 个元素都是负数,则定义其最大和为 0,输出整个序列的首尾元素。

示例 1

输入

6
-2 11 -4 13 -5 -2
10
-10 1 2 3 4 -5 -23 3 7 -21
6
5 -8 3 2 5 0
1
10
3
-1 -5 -2
3
-1 0 -2
0

输出

20 11 13
10 1 4
10 3 5
10 10 10
0 -1 -2
0 0 0

二、题解一

(1)当前面最大子序列和为负数的时候,后面的数加上 sum,其和一定会比后面本身更小,所以把 sum 重置为 0,重新寻找连续最大子序列和

(2)重新寻找的时候,也就是新序列开始的时候,记录下此时的数,可能是最大连续子序列和的开始数

(3)当新的连续最大子序列和大于之前的,说明之前的子序列不是所求,则更新序列的开始数和结尾数

三、代码实现(C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
using namespace std;

int main()
{
int sum, K, max;
while(cin >> K)
{
if(K == 0) break;
sum = 0, max = 0;
int nstart, start, nend, first, last;
cin >> nstart;
first = last = start = max = sum = nend = nstart;
for(int i = 1; i < K; i++)
{
int tmp;
cin >> tmp;
if(sum < 0) // 重新寻找最大连续子序列和
{
sum = 0;
start = tmp; // 记录序列的开始数
}
sum += tmp;
if(sum > max)
{
max = sum;
nstart = start;
nend = tmp;
}
if(i == K - 1)
{
last = tmp;
}
}
if(max < 0)
{
cout << 0 << " " << first << " " << last << endl;
}
else
{
cout << max << " " << nstart << " " << nend << endl;
}
}
return 0;
}

四、题解二

DP - 最大连续子序列和问题的变形

记 dp[i] - 以元素 ai(1 <= i <= n)结尾的连续子序列的和,则有两种情况:

  1. 这个最大和的连续子序列只有一个元素,即以 a[i] 开始,以 a[i] 结尾;
  2. 这个最大和的连续子序列有多个元素,即从前面某处 a[j] 开始(j < i),一直到 a[i] 结尾。

对第一种情况,最大和即 a[i] 本身;
对第二种情况,最大和即 dp[i-1] + a[i]。

于是得到状态转移方程:
dp[i] = max{ A[i], dp[i-1]+A[i] } (边界条件: dp[1] = a[1])

不过,这里要求判断输入全为负数的情况,并要求输出所求最大连续子序列的起点和终点元素,因此稍作处理即可,本质不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>
#define maxn 10010

int n, a[maxn];

struct DP // 以第 i 个元素为终点的连续子序列
{
int left; // 子序列起点
int right; // 子序列终点
int val; // 子序列和
}dp[maxn];

// 判断输入是否均为负数
int Judge(int *a, int n)
{
int i;
for(i = 1; i <= n; i++)
{
if(a[i] >= 0)
{
return 0;
}
}
return 1;
}

int main()
{
int i;
int maxLeft, maxRight, maxVal; // 记录最大连续子序列起点、终点、和
while(scanf("%d", &n) != EOF)
{
if(n == 0) break;
for(i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}

// 输入数据均为负数,则输出 0 及首尾元素
if(Judge(a, n))
{
printf("0 %d %d\n", a[1], a[n]);
}
else // 否则,递推
{
dp[1].left = dp[1].right = 1;
dp[1].val = a[1];
// 考察 a[i] 与 dp[i-1].val + a[i] 的大小关系
for(i = 2; i <= n; i++)
{
// 后者大,说明在 dp[i-1] 的基础上追加 a[i] 得到的连续子序列和更大
if(a[i] < dp[i-1].val + a[i])
{
dp[i].left = dp[i-1].left;
dp[i].right = i;
dp[i].val = dp[i-1].val + a[i];
}
else
{
// 前者大,说明此时 a[i] 独自成为一个连续子序列
dp[i].left = i;
dp[i].right = i;
dp[i].val = a[i];
}
}
// 在所有连续子序列中求解和最大的
maxLeft = dp[1].left;
maxRight = dp[1].right;
maxVal = dp[1].val;
for(i = 2; i <= n; i++)
{
if(dp[i].val > maxVal)
{
maxVal = dp[i].val;
maxLeft = dp[i].left;
maxRight = dp[i].right;
}
}
printf("%d %d %d\n", maxVal, a[maxLeft], a[maxRight]);
}
}
return 0;
}

题目链接
[编程题]最大连续子序列

评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...