最大子列和问题

最大子列和问题

给定K个整数组成的序列{ 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

 

解题思路

  第一种是暴力解法,按序列顺序每次选一项,然后从这项开始把后面的每一项相加,如果发现某个子项的和比之前记录的最大和要大,就更新最大和。

  AC代码:

 1 #include <cstdio>
 2 
 3 void maxSubSeqSum(int *a, int n);
 4 
 5 int main() {
 6     int n;
 7     scanf("%d", &n);
 8     int a[n];
 9     for (int i = 0; i < n; i++) {
10         scanf("%d", a + i);
11     }
12     maxSubSeqSum(a, n);
13     
14     return 0;
15 }
16 
17 void maxSubSeqSum(int *a, int n) {
18     int maxSum = 0;
19     for (int i = 0; i < n; i++) {
20         int ret = 0;
21         for (int j = i; j < n; j++) {
22             ret += a[j];
23             if (ret > maxSum) maxSum = ret;
24         }
25     }
26     
27     printf("%d", maxSum);
28 }

  第二种是分治解法。主要思想是,先求序列左半边的最大子列和,得到leftMaxSum。然后再求序列右半边的最大子列和,得到rightMaxSum。然后求中间部分的最大子列和,方法就是从序列中间项开始,分别求左右两边方向的部分最大子列和,得到左边的leftMax和右边的rightMax,他们的和,也就是leftMax + rightMax,就是中间部分的最大子列和。然后从leftMaxSum,rightMaxSum,leftMax + rightMax三者中选最大的那个,就是整个序列的最大子列和了。

  而上述所说的求序列左半边的最大子列和,以及求序列右半边的最大子列和用到的是同样的方法,所以其实就是递归调用。这就是求最大子列和的分治法。

  当时我第一次接触到分治法也不是很懂,觉得递归理解起来很难。但学了几个月的算法后,现在理解起来没有什么困难了。主要是不要往递归里面跳,就用我们平时的思考方式去理解好了。比如说,给你一个序列,求最大子列的和。我们正常的思考方式是:先知道左半部分的最大子列和以及右半部分的最大子列和,然后再知道中间部分的最大子列和,比较三种大小不就知道了整个序列的最大子列和了吗?而求左半部分和以及右半部分的最大子列也是用同样的方法,就拿左半部分为例:先知道左半部分的左半部分的最大子列和,以及左半部分的右半部分的最大子列和,然后再知道左半部分的中间部分的最大子列和......

  AC代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 
 4 int maxSubSeqSum(int *a, int low, int high); 
 5 
 6 int main() {
 7     int n;
 8     scanf("%d", &n);
 9     int a[n];
10     for (int i = 0; i < n; i++) {
11         scanf("%d", a + i);
12     }
13     printf("%d", maxSubSeqSum(a, 0, n - 1));
14     
15     return 0;
16 }
17 
18 int maxSubSeqSum(int *a, int low, int high) {
19     // 递归的终止条件,子列只有1个数字
20     if (low == high) {
21         if (a[low] >= 0) return a[low];
22         else return 0;
23     }
24     
25     int mid = low + high >> 1;                          // 找到中分点
26     int leftMaxSum = maxSubSeqSum(a, low, mid);         // 递归求得左半部分边子列的最大和
27     int rightMaxSum = maxSubSeqSum(a, mid + 1, high);   // 递归求得右半部分边子列的最大和
28     
29     int leftMax = 0, rightMax = 0, sum = 0;
30     // 从中线向左扫描
31     for (int i = mid; i >= low; i--) {
32         sum += a[i];
33         if (sum > leftMax) leftMax = sum;
34     }
35     sum = 0;
36     // 从中线向右扫描
37     for (int i = mid + 1; i <= high; i++) {
38         sum += a[i];
39         if (sum > rightMax) rightMax = sum;
40     }
41     
42     // 三者中最大的那个就是整个序列的最大子列和 
43     return std::max(std::max(leftMaxSum, rightMaxSum), leftMax + rightMax);
44 }

  第三种解法是最快的,叫“在线处理”。

  思路就是,只需要扫描整个序列一遍,累加每一项。如果发现某次累加的和比之前记录的最大和要大,则更新最大和;如果发现累加的和是负数,则当前的累加和置零,从下一项开始继续累加。

  AC代码如下:

 1 #include <cstdio>
 2 
 3 int main() {
 4     int n;
 5     scanf("%d", &n);
 6     int a[n];
 7     for (int i = 0; i < n; i++) {
 8         scanf("%d", a + i);
 9     }
10     int sum = 0, maxSum = 0;
11     for (int i = 0; i < n; i++) {
12         sum += a[i];
13         if (sum > maxSum) maxSum = sum;
14         else if (sum < 0) sum = 0;
15     }
16     printf("%d", maxSum);
17         
18     return 0;
19 }

  更新:

  其实这种方法就是用到了动态规划的思想,后面接触到动态规划才知道。

  下面给出最原始的动态规划的思路。

  首先设函数$dp(i)$表示为以第i个位置结尾的,连续的最大子列和。

  一开始把dp数组初始化为对应的a[i],表示一开始每个元素的最大子列和就是它本身,其中如果是负数的话要初始化为0。

  所以要知道以第i个位置为结尾的那个连续的最大子列和,就可以通过递推公式\[dp(i) = max(dp(i-1) + a[i], dp(i))\]来求得,最大的和是i的前一个位置也就是i-1的最大和dp[i-1]与a[i]的和,以及dp[i]中最大的那一个。

  AC代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 int main() {
 6     int n;
 7     scanf("%d", &n);
 8     int a[n];
 9     for (int i = 0; i < n; i++) {
10         scanf("%d", a + i);
11     }
12     
13     int dp[n];
14     for (int i = 0; i < n; i++) {
15         dp[i] = a[i] > 0 ? a[i] : 0;
16     }
17     for (int i = 1; i < n; i++) {
18         dp[i] = max(dp[i - 1] + a[i], dp[i]);
19     }
20     
21     int ret = 0;
22     for (int i = 0; i < n; i++) {
23         if (dp[i] > ret) ret = dp[i];
24     }
25     printf("%d", ret);
26     
27     return 0;
28 }

 

参考资料

  浙江大学——数据结构:https://www.icourse163.org/course/ZJU-93001?tid=1461682474

posted @ 2021-05-26 22:02  onlyblues  阅读(831)  评论(0编辑  收藏  举报
Web Analytics