最大连续子数组和

Posted on 2015-08-14 21:12  Maples7  阅读(1291)  评论(0编辑  收藏  举报

题目描述

给定一个数组a[0,...,n-1],求其最大连续子数组(长度>=1)和


输入描述

第一行一个整数n(1<=n<=100000),然后依次输入n个整数(每个整数范围[-5000, 5000])


输出描述

输出一个整数表示最大子数组和


样例输入

5
1 -1 1 1 -1


样例输出

2




1.

O(n)的算法可以用DP。

设sum[i]为以第i个元素结尾且和最大的连续子数组。
假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i] = max(sum[i-1] + a[i], a[i])。
可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。
由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。

代码如下:
 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main()
 5 {
 6     int n, num;
 7     long sum, max;
 8     cin >> n;
 9     cin >> sum;
10     max = sum;
11     for (int i = 1; i < n; i++)
12     {
13         cin >> num;
14         if (sum > 0)
15             sum += num;
16         else
17             sum = num;
18         if (sum > max)
19             max = sum;
20     }
21 
22     cout << max;
23 
24     return 0;
25 }

 

Reference:http://www.cnblogs.com/waytofall/archive/2012/04/10/2439820.html

 

 

2.

O(nlogn) 的算法可以用分治解决。

 

求一个数组a[low,high]的最大子数组,只有3种情况:
* 该子数组位于a[low,high]的左边,即位于a[low,mid]中,则可以递归的求解a[low,mid].
* 该子数组位于a[low,high]的右边,即位于a[mid+1,high]中,则可以递归的求解a[mid+1,high].
* 该子数组位于a[low,high]的中间,即包括了a[mid],则直接从中间求解中间向左右延伸的最大子数组和,详见getMiddle函数。
* 返回上面三种情况的最大值,则是我们要求解的答案。
 
对于每一个子问题,即需要求的当前区间的最大连续子数组和,都逃不过以上三种可能性,而当前子问题的最优解,又构成以该子问题为基础的规模更大的子问题的一部分,所以向上回溯也可得到全局最优解,算法正确性得证。
 
代码如下:
 1 class Solution:
 2     def getMidSum(self, lo, hi, mid):
 3         leftMax = float("-inf")
 4         rightMax = float("-inf")
 5         sum, i = 0, mid - 1
 6         while i > lo - 1:
 7             sum += self.nums[i]
 8             if sum > leftMax:
 9                 leftMax = sum
10             i -= 1
11         sum, i = 0, mid
12         while i < hi:
13             sum += self.nums[i]
14             if sum > rightMax:
15                 rightMax = sum
16             i += 1
17         return leftMax + rightMax
18 
19     def divConq(self, lo, hi):
20         if hi - lo < 2:
21             return self.nums[lo]
22         else:
23             mid = (lo + hi) >> 1
24             leftSum = self.divConq(lo, mid)
25             rightSum = self.divConq(mid, hi)
26             midSum = self.getMidSum(lo, hi, mid)
27             return max(leftSum, rightSum, midSum)
28 
29     # @param {integer[]} nums
30     # @return {integer}
31     def maxSubArray(self, nums):
32         self.nums = nums
33         return self.divConq(0, len(nums))

 

在求 getMidSum 的时候,有两点需要注意:

1. 从 mid 向两边求和,并不能简单的以 “当这个元素为正的时候就加进去,而遇到负数的时候就退出循环” 这样的策略,因为显然即便是包括mid元素在内的一段元素全部为正数的区间,也不并不一定是包含mid元素在内的最大连续和子区间(即便不要求包含 mid 也是如此)。倒是可以用 DP 的策略,但如果这样也没必要用 分治 了。对应的反例是 [1,2,-1,-2,2,1,-2,1,4,-5,4]。

2. 需考虑当区间长度小于等于 2 的情况不要留下 bug。因为 6-10行 以及 12-16行代码 的循环是必须要进入一次的,这样才能保证 leftMax 和 rightMax 被更新而不至于在已经需要函数返回的时候还保留是无穷大的值。也就是说,这两个循环所涉及的区间,至少要保证其中有元素。因为我所有的区间定义都是“左闭右开”式的,也就是 [lo, hi) 式的。所以当区间长度为 2 时,假设是 [0, 2),那么 mid 为 1 (也就是mid值对于偶数长度区间其实是“右偏”的),所以我特意把对于 mid 的求和划到了右区间来做,这样保证第二个循环至少能进入一次。如果这里不这样做,则可能致使 rightMax 依然保持着负无穷的值,那么整个函数都会返回负无穷,这是不可取的。对应的反例是 [1,2]。