【ToReadList】六种姿势拿下连续子序列最大和问题,附伪代码(以HDU 1003 1231为例)(转载)

问题描述:

      连续子序列最大和,其实就是求一个序列中连续的子序列中元素和最大的那个。

比如例如给定序列:

{ -2, 11, -4, 13, -5, -2 }

其最大连续子序列为{ 11, -4, 13 },最大和为20。

===============================================================

问题分析

1.首先最朴素的方法是暴力 O(n^3)

直接两个for循环枚举子序列的首尾,然后再来个循环计算序列的和,每次更新和的最大值。

但是这种方法的复杂度是O(n^3),效率实在太低了。。。

————————————————————————————————————————————————

2.第二种方法是预处理 O(n^2)

在读入的时候将前面数的和放在数组中,就能得到一个数组sum[i]储存前i个数的和。然后两重循环枚举首尾,利用sum数组迅速求出子序列的和。

其实这种方法只是优化了前面那种方法的计算和的循环,复杂的是O(n^2),也很糟糕。
————————————————————————————————————————————————

3.第三种是利用分治思想 O(nlogn)

分治算法但看代码不是很好理解,其实思想很简单,就是把序列分成两块计算,用递归分别求出两块序列中的最大子序列和,然后从序列中间向两边遍历求出包含中心的序列的最大和。返回最大的那个序列和。

递归真的很神奇,一直把问题分解乘小问题交给下一层递归处理,直到最底层。

用分治算法的复杂度好了一些,是O(nlogn),虽然不是最优解,但是理解这种算法的确能让我们对递归理解得更加深刻。
————————————————————————————————————————————————

4.第四种是累积遍历算法 O(n)

遍历序列的时候对Sum进行累计,如果Sum累积后小于0的话就把Sum重置为负无穷,每次更新Sum的最大值。最后便能求出最大值。

其实这个算法就是把序列分为好几块,每一块满足:对于任意k,前k个数的和不会小于0(小于0就会分裂成两块了),当前i个数的和大于最大值时就进行更新,而最大值的左边界就是该块序列的第一个,右边界是第i个。

时间复杂度为O(n),而且可以一边读取一边处理,不需要开辟数组来存,空间也很省。

------------------------------------------------------------------------------

举个例子:

-10   1  2  3  4  -5   -23   3  7    -21   (num)

-10 | 1  3  6 10  8 | -23 |  3 10 | -21   (Sum)(|号为该处Sum被清零)

由于10是Sum的最大值,所以,红色的那块就是要求的范围了。

------------------------------------------------------------------------------

    但是为什么所求序列为什么是在序列块中并且是以序列块第一个数作为开头呢?

:如果不是这样的话,会有几种情况:

1  该目标序列跨越了几个块。由于Sum在<=0的时候会重置为负无穷,如果跨块的话,没有重置那它的和肯定不会是最大的。

2  该目标序列在序列块中间,且目标序列的开头并不是序列的第一个。由于序列块前k个数的和不会小于0,所以这样肯定没有从第一个开始来的大。

————————————————————————————————————————————————

5.第五种是动态规划 O(n)

dp做法是很普遍的做法,只要想出状态转移方程就可以很快做出来了。

状态转移方程:sum[i] = max{sum[i-1]+a[i],a[i]}. (sum[i]记录以a[i]为子序列末端的最大连续和。)在dp的过程中便可以更新sum数组的最大值以及两个边界。

其实完全可以不用开数组,累计sum直到sum + a < a,把sum赋值为a,更新最大值就行了。你会发现这跟第4种方法是一样的。。。只是判断条件不一样,一个是sum <= 0一个是sum + a < a。。。(其实是一样的。。。)所以复杂度和第四种是一样的都是O(n)。

————————————————————————————————————————————————

6.第六种是第二种累计求和 O(n)

(感谢@shuangde800 大神的指导)

这种方法跟第4种一样是累计求和。我们可以发现,连续子序列的和其实就是 (到尾端的和)-(到首端-1的和),要让这个式子最大其实可以让(到首端-1的和)尽量的小,我们可以遍历数组,维护(到首端-1的和)的最小值。

理解起来就是遍历的时候,找出以当前位置为尾端,以此前的某一点为首端的 和最大的子序列,然后更新最大值。(其实这个思想和第五种方法是异曲同工的)

由于只需要一遍遍历,所以复杂度为O(n)。

===============================================================

伪代码:

由于下面的Solution里面我的代码太挫,于是先贴出伪代码。

1.暴力 O(n^3):

[plain] view plaincopyprint?

  1. max←(-∞)
  2. for i←1 to len do
  3. for j←i to len do
  4. sum←0
  5. // 求和
  6. for k←i to j
  7. sum←sum+arr[i]
  8. end for
  9. // 更新最大值
  10. if sum>max then
  11. max←sum
  12. end if
  13. end for
  14. end for
  15. return max

2.预处理 O(n^2):

[plain] view plaincopyprint?

  1. // 记录前i项和
  2. sum[0]←0
  3. for i←1 to len do
  4. sum[i]←sum[i-1]+arr[i]
  5. end for
  6. // 枚举首尾
  7. max←(-∞)
  8. for i←1 to len do
  9. for j←i to len do
  10. // 更新最大值
  11. if sum[j]-sum[i-1]>max then
  12. max←sum[j]-sum[i-1]
  13. end if
  14. end for
  15. end for
  16. return max

3.分治算法 O(nlogn)

[plain] view plaincopyprint?

  1. function maxsum(arr,left,right)
  2. // 只有一个元素就返回
  3. if right-left=1 then
  4. return
  5. end if
  6. // 划分[left,mid)和[mid,left)分治
  7. mid←x+(y-x)/2
  8. L←maxsum(arr,left,mid)
  9. R←maxsum(arr,mid,left)
  10. if L>R then
  11. max←L
  12. else
  13. max←R
  14. end if
  15. // 求出处于中间的连续子序列最大和
  16. // 先求从中间向左最大值
  17. tempSum←0
  18. leftSum←arr[m-1]
  19. for i←m-1 downto right do
  20. tempSum←tempSum+arr[i]
  21. if leftSum<tempSum then
  22. leftSum←tempSum
  23. end if
  24. end for
  25. // 再求从中间向右最大值
  26. tempSum←0
  27. rightSum←arr[m]
  28. for i←m to left do
  29. tempSum←tempSum+arr[i]
  30. if rightSum<tempSum then
  31. rightSum←tempSum
  32. end if
  33. end for
  34. midSum←leftSum+rightSum
  35. if max<midSum then
  36. max←midSum
  37. end if
  38. return max
  39. end function

4.累积遍历算法 O(n)

[plain] view plaincopyprint?

  1. sum←0
  2. max←arr[0]
  3. for i←1 to len do
  4. if sum<=0 then
  5. sum←a[i]
  6. end if
  7. if max<sum then
  8. max←sum
  9. end if
  10. end for
  11. return max

5.动态规划 O(n)

[plain] view plaincopyprint?

  1. sum←(-∞)
  2. max←(-∞)
  3. for i←1 to len do
  4. if sum+arr[i]<arr[i] then
  5. sum←arr[i]
  6. else
  7. sum←sum+arr[i]
  8. end if
  9. if max<sum then
  10. max←sum
  11. end if
  12. end for
  13. return max

6.第二种累计求和 O(n)

[plain] view plaincopyprint?

  1. max←-INF
  2. recMin←0
  3. for i←1 to len do
  4. sum←sum+arr[i]
  5. temp←sum-recMin
  6. // 更新max
  7. if temp>max then
  8. max←temp
  9. end if
  10. // 更新recMin
  11. if recMin>sum then
  12. recMin←sum
  13. end if
  14. end for

===============================================================

Solution:

hdu这两题都是要求边界的。

代码如下:

hdu 1003:

题意:求出给出序列中和最大的连续子序列,以及该序列的左边界和右边界

累积遍历算法(会TLE)

[cpp] view plaincopyprint?

  1. /*
  2. *   Author:        illuz <[email protected]>
  3. *   Blog:          http://blog.csdn.net/hcbbt
  4. *   File:          3.cpp
  5. *   Lauguage:      C/C++
  6. *   Create Date:   2013-08-28 01:12:27
  7. *   Descripton:    max sum of continuous sequence, simulation
  8. */
  9. #include <cstdio>
  10. #define rep(i, n) for (int i = 0; i < (n); i++)
  11. const int MAXN = 100100;
  12. int k, a[MAXN];
  13. int main() {
  14. int n;
  15. scanf("%d", &n);
  16. rep(cas, n) {
  17. scanf("%d", &k);
  18. rep(i, k) scanf("%d", &a[i]);
  19. int res = a[0], rl = 0, rr = 0;
  20. int sum = 0, l = 0;
  21. rep(i, k) {
  22. if (sum < 0) {
  23. sum = a[i];
  24. l = i;
  25. }
  26. else
  27. sum += a[i];
  28. if (res < sum) res = sum, rl = l, rr = i;
  29. }
  30. if (cas) printf("\n");
  31. printf("Case %d:\n", cas + 1);
  32. printf("%d %d %d\n", res, rl + 1, rr + 1);
  33. }
  34. return 0;
  35. }

动态规划

[cpp] view plaincopyprint?

  1. /*
  2. *   Author:        illuz <[email protected]>
  3. *   Blog:          http://blog.csdn.net/hcbbt
  4. *   File:          4.cpp
  5. *   Lauguage:      C/C++
  6. *   Create Date:   2013-08-28 15:23:30
  7. *   Descripton:    max sum of continuous sequence, dp
  8. */
  9. #include <cstdio>
  10. #define rep(i, n) for (int i = 0; i < (n); i++)
  11. int main() {
  12. int l, R, L, Max, sum, k, t, cas, n;
  13. scanf("%d", &n);
  14. rep(cas, n) {
  15. sum = Max = -0xfffffff;
  16. scanf("%d", &k);
  17. rep (i, k) {
  18. scanf("%d", &t);
  19. if (sum + t < t) sum = t, l = i;
  20. else sum += t;
  21. if (Max < sum) Max = sum, L = l, R = i;
  22. }
  23. if (cas) printf("\n");
  24. printf("Case %d:\n", cas + 1);
  25. printf("%d %d %d\n", Max, L + 1, R + 1);
  26. }
  27. return 0;
  28. }

第二种累计求和

[cpp] view plaincopyprint?

  1. /*
  2. *   Author:        illuz <[email protected]>
  3. *   Blog:          http://blog.csdn.net/hcbbt
  4. *   File:          6.cpp
  5. *   Lauguage:      C/C++
  6. *   Create Date:   2013-08-29 00:46:25
  7. *   Descripton:    o(n)
  8. */
  9. #include <cstdio>
  10. int n, a[1000000];
  11. int max, recMin, sum, l, recL, recR;
  12. int main() {
  13. int k;
  14. scanf("%d", &k);
  15. for (int cas = 1; cas <= k; cas++) {
  16. scanf("%d", &n);
  17. for (int i = 0; i < n; i++) scanf("%d", &a[i]);
  18. max = -0xfffffff;
  19. sum = 0;
  20. recMin = 0;
  21. l = -1;
  22. recL = -1, recR = 0;
  23. for (int i = 0; i < n; i++) {
  24. sum += a[i];
  25. if (sum - recMin > max) {
  26. max = sum - recMin;
  27. recL = l;
  28. recR = i;
  29. }
  30. if (recMin > sum) {
  31. recMin = sum;
  32. l = i;
  33. }
  34. }
  35. if (cas - 1) printf("\n");
  36. printf("Case %d:\n%d %d %d\n", cas, max, recL + 2, recR + 1);
  37. }
  38. return 0;
  39. }

————————————————————————————————————————————————

hdu 1231:

题意:求连续子序列最大和及上下界的值,输出还得处理下

预处理

[cpp] view plaincopyprint?

  1. /*
  2. *   Author:        illuz <[email protected]>
  3. *   Blog:          http://blog.csdn.net/hcbbt
  4. *   File:          hdu1231.cpp
  5. *   Lauguage:      C/C++
  6. *   Create Date:   2013-08-27 17:06:16
  7. *   Descripton:    hdu1231, max sum of continuous sequence, pre, O(n^2)
  8. */
  9. #include <cstdio>
  10. #define rep(i, n) for (int i = 0; i < (n); i++)
  11. #define repf(i, a, b) for (int i = (a); i <= (b); i++)
  12. const int MAXN = 10010;
  13. int k, s[MAXN], a[MAXN];
  14. int main() {
  15. while (scanf("%d", &k) && k) {
  16. s[0] = 0;
  17. repf(i, 1, k) {
  18. scanf("%d", &a[i]);
  19. s[i] = s[i - 1] + a[i];
  20. }
  21. int ans, l, r;
  22. ans = l = r = s[1];
  23. repf(i, 1, k) repf(j, i, k)
  24. if (ans < s[j] - s[i - 1]) {
  25. ans = s[j] - s[i - 1];
  26. l = a[i];
  27. r = a[j];
  28. }
  29. if (ans >= 0)
  30. printf("%d %d %d\n", ans, l, r);
  31. else
  32. printf("0 %d %d\n", a[1], a[k]);
  33. }
  34. return 0;
  35. }

分治思想

[cpp] view plaincopyprint?

  1. /*
  2. *   Author:        illuz <[email protected]>
  3. *   Blog:          http://blog.csdn.net/hcbbt
  4. *   File:          2_hdu1231.cpp
  5. *   Lauguage:      C/C++
  6. *   Create Date:   2013-08-27 17:18:49
  7. *   Descripton:    hdu1231, max sum of continuous sequence, partition
  8. */
  9. #include <cstdio>
  10. #define rep(i, n) for (int i = 0; i < (n); i++)
  11. const int MAXN = 10010;
  12. struct ANS {
  13. int sum;
  14. int l, r;
  15. ANS() {}
  16. ANS(int a, int b, int c) : sum(a), l(b), r(c) {}
  17. };
  18. ANS maxsum(int* a, int x, int y) {
  19. int m, v, L, R, l, r;
  20. ANS M(a[x], a[x], a[x]), t1, t2;
  21. if (y - x == 1)
  22. return M;
  23. m = x + (y - x) / 2;
  24. t1 = maxsum(a, x, m);
  25. t2 = maxsum(a, m, y);
  26. if (t1.sum >= t2.sum) M = t1;
  27. else M = t2;
  28. v = 0; L = a[m - 1]; l = a[m - 1];
  29. for (int i = m - 1; i >= x; i--){
  30. v += a[i];
  31. if (v > L) L = v, l = a[i];
  32. }
  33. v = 0; R = a[m]; r = a[m];
  34. for (int i = m; i < y; i++) {
  35. v += a[i];
  36. if (v > R) R = v, r = a[i];
  37. }
  38. if (M.sum > (L + R))
  39. return M;
  40. return ANS(L + R, l, r);
  41. }
  42. int k, a[MAXN];
  43. int main() {
  44. while (scanf("%d", &k) && k) {
  45. rep(i, k) scanf("%d", &a[i]);
  46. ANS ans = maxsum(a, 0, k);
  47. if (ans.sum >= 0)
  48. printf("%d %d %d\n", ans.sum, ans.l, ans.r);
  49. else
  50. printf("0 %d %d\n", a[0], a[k - 1]);
  51. }
  52. return 0;
  53. }

累积遍历算法

[cpp] view plaincopyprint?

  1. /*
  2. *   Author:        illuz <[email protected]>
  3. *   Blog:          http://blog.csdn.net/hcbbt
  4. *   File:          3.cpp
  5. *   Lauguage:      C/C++
  6. *   Create Date:   2013-08-28 01:12:27
  7. *   Descripton:    max sum of continuous sequence, simulation
  8. */
  9. #include <cstdio>
  10. #define rep(i, n) for (int i = 0; i < (n); i++)
  11. const int MAXN = 10010;
  12. int k, a[MAXN];
  13. int main() {
  14. while (scanf("%d", &k) && k) {
  15. rep(i, k) scanf("%d", &a[i]);
  16. int res = a[0], rl = a[0], rr = a[k - 1];
  17. int sum = 0, l = 0;
  18. rep(i, k) {
  19. if (sum <= 0) {
  20. sum = a[i];
  21. l = a[i];
  22. }
  23. else
  24. sum += a[i];
  25. if (res < sum) res = sum, rl = l, rr = a[i];
  26. }
  27. if (res >= 0)
  28. printf("%d %d %d\n", res, rl, rr);
  29. else
  30. printf("0 %d %d\n", a[0], a[k - 1]);
  31. }
  32. return 0;
  33. }

动态规划

[cpp] view plaincopyprint?

  1. /*
  2. *   Author:        illuz <[email protected]>
  3. *   Blog:          http://blog.csdn.net/hcbbt
  4. *   File:          4.cpp
  5. *   Lauguage:      C/C++
  6. *   Create Date:   2013-08-28 15:11:46
  7. *   Descripton:    max sum of continuous sequence, dp
  8. */
  9. #include <cstdio>
  10. #define rep(i, n) for (int i = 0; i < (n); i++)
  11. int main() {
  12. int l, R, L, Max, sum, k, t;
  13. while (scanf("%d", &k) && k) {
  14. sum = Max = -0xfffffff;
  15. rep (i, k) {
  16. scanf("%d", &t);
  17. if (sum + t < t) sum = t, l = t;
  18. else sum += t;
  19. if (Max < sum) Max = sum, L = l, R = t;
  20. }
  21. if (Max >= 0)
  22. printf("%d %d %d\n", Max, L, R);
  23. else
  24. printf("0 %d %d\n", L, t);
  25. }
  26. return 0;
  27. }

第二种累计求和

[cpp] view plaincopyprint?

    1. /*
    2. *   Author:        illuz <[email protected]>
    3. *   Blog:          http://blog.csdn.net/hcbbt
    4. *   File:          6.cpp
    5. *   Lauguage:      C/C++
    6. *   Create Date:   2013-08-29 00:08:52
    7. *   Descripton:    o(n)
    8. */
    9. #include <cstdio>
    10. int n, a[1000000];
    11. int max, recMin, sum, l, recL, recR;
    12. int main() {
    13. while (scanf("%d", &n) && n) {
    14. for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    15. max = -0xfffffff;
    16. sum = 0;
    17. recMin = 0;
    18. l = -1;
    19. recL = -1, recR = 0;
    20. for (int i = 0; i < n; i++) {
    21. sum += a[i];
    22. if (sum - recMin > max) {
    23. max = sum - recMin;
    24. recL = l;
    25. recR = i;
    26. }
    27. if (recMin > sum) {
    28. recMin = sum;
    29. l = i;
    30. }
    31. }
    32. if (max >= 0)
    33. printf("%d %d %d\n", max, a[recL + 1], a[recR]);
    34. else
    35. printf("0 %d %d\n", a[0], a[n - 1]);
    36. }
    37. return 0;
    38. }
时间: 2024-10-12 12:09:30

【ToReadList】六种姿势拿下连续子序列最大和问题,附伪代码(以HDU 1003 1231为例)(转载)的相关文章

连续子序列最大和问题的四种经典解答

问题描述 给定(可能是负的)整数序列A1, A2,...,AN, 寻找(并标识)使Sum(Ak)(k >=i, k <= j)的值最大的序列.如果所有的整数都是负的,那么连续子序列的最大和是那个最大的负数项.最好给出给出最大和连续子序列!! 1 暴力破解法 这个问题有一个最简单直接的穷举解决法.我们看问题,既然要求里面最大的连续子序列.那么所有的连续子序列将由哪些组成呢?以数组的第一个元素为例,连续子序列必须是至少包含元素A1,也可能包含从A1到A2...以及从A1到AN.这样就有N种可能.后

连续子序列的最大乘积

问题描述 给定一个整数序列,序列中可能含有0,正数,负数,求出连续子序列乘积的最大值 暴力解法 双层循环,一一遍历,每次将当前结果与前次结果要比较,如果大于前次结果,更新最大值,时间复杂度为O(n2),复杂度较高,代码略 动态规划解法 对于序列arr,maxDp[k]表示以arr[k](必须包含arr[k])结尾的最大连续子序列乘积值,minDp[k]表示以arr[k](必须包含arr[k])结尾的最小连续子序列乘积值,则对于maxDp[k + 1],表示以arr[k + 1]结尾的最大连续子序

动态规划:最大连续子序列乘积

题目描述: 给定一个浮点数序列(可能有正数.0和负数),求出一个最大的连续子序列乘积. 分析:若暴力求解,需要O(n^3)时间,太低效,故使用动态规划. 设data[i]:第i个数据,dp[i]:以第i个数结尾的连续子序列最大乘积, 若题目要求的是最大连续子序列和,则易确定状态转移方程为: dp[i]=max(data[i],dp[i-1]+data[i])(dp[i]为以第i个数结尾的连续子序列最大和) 但乘积存在负负得正的问题,即原本很小的负数成了一个负数反而变大了,(负数逆袭了), 故不能

动态规划--连续子序列的最大和

给定k个整数的序列{N1,N2,...,Nk },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= k.最大连续子序列是所有连续子序中元素和最大的一个,例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{11,-4,13},最大连续子序列和即为20. 注:为方便起见,如果所有整数均为负数,则最大子序列和为0. 算法一,穷举法,找出所有子数组,然后求出子数组的和,在所有子数组的和中取最大值 /*O(n^

hdu1231 最大连续子序列

最大连续子序列 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 22849    Accepted Submission(s): 10135 Problem Description 给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j

HDU1231 最长连续子序列

最大连续子序列 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 31687    Accepted Submission(s): 14214 Problem Description 给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j

动态规划:最大连续子序列和

问题:给出一个数组,求其连续子序列的最大和 package 动态规划; /** * 给出一个数组,求其连续子数组的最大和 * @author Administrator * */ public class MaxSum { public static void main(String[] args) { int[] arr = new int[]{-3,1,-3,4,-1,2,1}; int max=arr[0]; int current=arr[0]; for(int i=1;i<arr.le

杭电 1231 最大连续子序列

http://acm.hdu.edu.cn/showproblem.php?pid=1231 最大连续子序列 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 18622    Accepted Submission(s): 8273 Problem Description 给定K个整数的序列{ N1, N2, ..., NK },其任意

HDU 1231 最大连续子序列 (动规)

最大连续子序列 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 18316    Accepted Submission(s): 8127 Problem Description 给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j