Maximum Subsequence Sum 最大子序列和的进击之路
本文解决最大子序列和问题,有两个题目组成,第二个题目比第一个要求多一些(其实就是要求输出子序列首尾元素)。
01-复杂度1 最大子列和问题 (20分)
给定KK个整数组成的序列{ N1, N2, ..., NK },“连续子列”被定义为{ Ni, Ni+1 ..., Nj },其中 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。
本题旨在测试各种不同的算法在各种数据情况下的表现。各组测试数据特点如下:
- 数据1:与样例等价,测试基本正确性;
- 数据2:102个随机整数;
- 数据3:103个随机整数;
- 数据4:104个随机整数;
- 数据5:105个随机整数;
输入格式:
输入第1行给出正整数K≤100000;第2行给出K个整数,其间以空格分隔。
输出格式:
在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出0。
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20
这道题解决方法的算法复杂度可以从O(N^3)、O(N^2)、O(NlogN)优化到O(N),以下是代码:
O(N^3):
1 #include <stdio.h>
2
3 /* O(N^3) Maxium Subsequence */
4
5 int main(){
6 int seq[100001];
7 int n, i, j, k;
8 int tmpsum;
9 int maxsum = 0;
10 scanf("%d", &n);
11 for(i = 0; i < n; i++){
12 scanf("%d", &seq[i]);
13 }
14 for(i = 0; i < n; i++){
15 for(j = i; j < n; j++){
16 tmpsum = 0;
17 for(k = i; k < n; k++){
18 tmpsum += seq[k];
19 if(tmpsum > maxsum )
20 maxsum = tmpsum;
21 }
22 }
23 }
24 printf("%d", maxsum);
25 return 0;
26 }
O(N^2):
1 #include <stdio.h>
2
3 /* O(N^2) Maxium Subsequence */
4
5 int main(){
6 int seq[100001];
7 int n, i, j;
8 int tmpsum;
9 int maxsum = 0;
10 scanf("%d", &n);
11 for(i = 0; i < n; i++){
12 scanf("%d", &seq[i]);
13 }
14 for(i = 0; i < n; i++){
15 tmpsum = 0;
16 for(j = i; j < n; j++){
17 tmpsum += seq[j];
18 if(tmpsum > maxsum )
19 maxsum = tmpsum;
20 }
21 }
22 printf("%d", maxsum);
23 return 0;
24 }
第三种算法采取了分治法,将序列一分为二,然后不断递归,再将左右边分别一分为二,不断切割,以(seq[left] == seq[right])为暂停标志,由此求出左子列最大和以及右子列最大和,同时,以中点为中心,分别向左右两边扫描,分别求出maxleftbodersum 和 maxrightbodersum, 之后得到跨越边界最大和 maxleftbodersum + maxrightbodersum, 最后比较 maxleftsum、 maxrightsum、maxleftbodersum + maxrightbodersum 三者取最大。可以发现,在递归中,结构是一样的,比如: 在左子列分割过程中,也不断地在重复以上过程,因此采用递归。
O(NlogN):
1 #include <stdio.h>
2 int Max3(int A, int B, int C){
3 return A > B ? A > C ? A : C : B > C ? B : C;
4 }
5 int Divide(int list[], int left, int right){
6 int MaxLeftsum, MaxRightsum;
7 int LeftBodersum, RightBodersum;
8 int MaxLeftBodersum, MaxRightBodersum;
9 int center, i;
10 if(left == right){
11 if(list[left] > 0)
12 return list[left];
13 else
14 return 0;
15 }
16 center = (left + right) / 2;
17 MaxLeftsum = Divide(list, left, center);
18 MaxRightsum = Divide(list, center + 1, right);
19 MaxLeftBodersum = 0;
20 LeftBodersum =0;
21 MaxRightBodersum = 0;
22 RightBodersum = 0;
23 for(i = center; i >= left; i--){
24 LeftBodersum += list[i];
25 if(LeftBodersum > MaxLeftBodersum)
26 MaxLeftBodersum = LeftBodersum;
27 }
28 for(i = center + 1; i <= right; i++){
29 RightBodersum += list[i];
30 if(RightBodersum > MaxRightBodersum)
31 MaxRightBodersum = RightBodersum;
32
33 }
34 return Max3(MaxLeftsum, MaxRightsum, MaxRightBodersum + MaxLeftBodersum );
35 }
36
37 int MaxSubseq( int list[], int N){
38 return Divide(list, 0, N - 1);
39 }
40 int main(){
41 int seq[100001];
42 int n;
43 int i;
44 scanf("%d", &n);
45 for(i = 0; i < n; i++ ){
46 scanf("%d", &seq[i]);
47
48 }
49 printf("%d\n", MaxSubseq(seq, n));
50
51 return 0;
52 }
O(N):
1 #include <stdio.h>
2
3 /* O(N) Maxium Subsequence */
4
5 int main(){
6 int seq[100001];
7 int n, i;
8 int tmpsum = 0;
9 int maxsum = 0;
10 scanf("%d", &n);
11 for(i = 0; i < n; i++){
12 scanf("%d", &seq[i]);
13 }
14 for(i = 0; i < n; i++){
15 tmpsum += seq[i];
16 if(tmpsum > maxsum)
17 maxsum = tmpsum;
18 else if(tmpsum < 0)
19 tmpsum = 0;
20 }
21 printf("%d", maxsum);
22 return 0;
23 }
那么接下来在PAT甲级测试的1008 Maximum Subsequence Sum (25分)中,将这个问题难度稍微加大一些,要求输出子序列最小首尾元素(因为可能出现并列和相等的子序列)。原题如下
1007. Maximum Subsequence Sum (25)
Given a sequence of K integers { N1, N2, ..., NK }. A continuous subsequence is defined to be { Ni, Ni+1, ..., Nj } where 1 <= i <= j <= K. 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 -21Sample Output:
10 1 4
最直观的感觉,这道题用第四种O(N)算法最好,用第三种算法反倒不太方便。只不过需要增加记录子序列开始的index和结束的index。
代码如下:
1 #include <stdio.h>
2 int main(){
3 int n,i;
4 int seq[100001];
5 scanf("%d", &n);
6 for(i = 0; i < n; i++){
7 scanf("%d", &seq[i]);
8 }
9 int start = 0;
10 int end = 0;
11 int tmpindex = 0;
12 int tmpsum = 0;
13 int maxsum = -1;
14 //flag is a symbol to check whether every element in seq is nonnegative
15 int flag = 0;
16
17 for(i = 0; i < n; i++){
18 if(seq[i] >= 0){
19 flag = 1;
20 }
21 tmpsum += seq[i];
22 if(tmpsum > maxsum){
23 maxsum = tmpsum;
24 end = i;
25 start = tmpindex;
26 }else if(tmpsum < 0){
27 tmpsum = 0;
28 tmpindex = i + 1;
29 }
30
31 }
32 if(flag == 0){
33 printf("0 %d %d\n", seq[0], seq[n - 1]);
34
35 }else{
36 printf("%d %d %d\n", maxsum, seq[start], seq[end] );
37 }
38
39
40 return 0;
41 }
上面代码中,关键是maxsum的初始值,一定是-1而不是0,我最初的错误就犯在这里,当初始maxsum = 0时,始终有一项AC不了,即当序列中只有一个0且其他全为负数时,当seq[i] = 0, tmp = 0时,如果maxsum是0,就进入不了第一个if判断,这是最容易疏忽的一点。