连续子数组最大和问题

冷不防 2022-07-18 01:12 264阅读 0赞

1. 问题描述

输入一个整形数组,求数组中连续的子数组使其和最大。比如,数组x
132021534183635.png

应该返回 x[2..6]的和187.

2. 问题解决

我们很自然地能想到穷举的办法,穷举所有的子数组的之和,找出最大值。

穷举法

i, j的for循环表示x[i..j],k的for循环用来计算x[i..j]之和。

  1. maxsofar = 0
  2. for i = [0, n)
  3. for j = [i, n)
  4. sum = 0
  5. for k = [i, j]
  6. sum += x[k]
  7. /* sum is sum of x[i..j] */
  8. maxsofar = max(maxsofar, sum)

有三层循环,穷举法的时间复杂度为O(n3) O(n3)

对穷举法的改进1

我们注意到x[i..j]之和 = x[i..j-1]之和 + x[j],因此在j的for循环中,可直接求出sum。

  1. maxsofar = 0
  2. for i = [0, n)
  3. sum = 0
  4. for j = [i, n)
  5. sum += x[j]
  6. /* sum is sum of x[i..j] */
  7. maxsofar = max(maxsofar, sum)

显然,改进之后的时间复杂度变为O(n2) O(n2)。

对穷举法的改进2

在计算fibonacci数时,应该还有印象:用一个累加数组(cumulative array)记录前面n-1次之和,计算当前时只需加上n即可。同样地,我们用累加数组cumarr记录:cumarr[i] = x[0] + . . . +x[i],那么x [i.. j]之和 = cumarr[j] -cumarr[i - 1]

  1. cumarr[-1] = 0
  2. for i = [0, n)
  3. cumarr[i] = cumarr[i-1] + x[i]
  4. maxsofar = 0
  5. for i = [0, n)
  6. for j = [i, n)
  7. sum = cumarr[j] - cumarr[i-1]
  8. /* sum is sum of x[i..j] */
  9. maxsofar = max(maxsofar, sum)

时间复杂度依然为O(n2) O(n2)。

分治法

所谓分治法,是指将一个问题分解为两个子问题,然后分而解决之。具体步骤如下:

  • 先将数组分为两个等长的子数组a, b;
    132024246849621.png
  • 分别求出两个数组a,b的连续子数组之和;
    132024304968346.png
  • 还有一种情况比较容易忽略:有可能最大和的子数组跨越两个数组;
    132024373096197.png
  • 最后比较ma ma, mb mb, mc mc,取最大即可。

在计算mc mc时,注意:mc mc必定包含总区间的中间元素,因此求mc mc等价于从中间元素开始往左累加的最大值 + 从中间元素开始往右累加的最大值

  1. float maxsum3(l, u)
  2. if (l > u) /* zero elements */
  3. return 0
  4. if (l == u) /* one element */
  5. return max(0, x[l])
  6. m = (l + u) / 2
  7. /* find max crossing to left */
  8. lmax = sum = 0
  9. for (i = m; i >= l; i--)
  10. sum += x[i]
  11. lmax = max(lmax, sum)
  12. /* find max crossing to right */
  13. rmax = sum = 0
  14. for i = (m, u]
  15. sum += x[i]
  16. rmax = max(rmax, sum)
  17. return max(lmax+rmax,
  18. maxsum3(l, m),
  19. maxsum3(m+1, u));

容易证明,时间复杂度为O(n∗log n) O(n∗log n)。

Kadane算法

Kadane算法又被称为扫描法,该算法用到了一个启发式规则:如果前面一段连续子数组的和小于0,那么就丢弃它。其实也蛮好理解的,举个简单例子,比如:数组-1, 2, 3,-1为负数,为了使得子数组之和最大,显然不应当把-1计入进内。

max_ending_here记录前面一段连续子数组之和。

  1. Initialize:
  2. max_so_far = 0
  3. max_ending_here = 0
  4. Loop for each element of the array
  5. (a) max_ending_here = max_ending_here + x[i]
  6. (b) if(max_ending_here < 0)
  7. max_ending_here = 0
  8. (c) if(max_so_far < max_ending_here)
  9. max_so_far = max_ending_here
  10. return max_so_far

只遍历了一遍数组,因此时间复杂度为O(n) O(n)。

3. 参考资料

[1] Jon Bentley, Programming Pearls.
[2] GeeksforGeeks, Largest Sum Contiguous Subarray.

发表评论

表情:
评论列表 (有 0 条评论,264人围观)

还没有评论,来说两句吧...

相关阅读

    相关 数据结构-连续数组

    【题目来自灰灰考研】 在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的