题目描述
给定一个数组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) 的算法可以用分治解决。
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]。
[ by Maples7 ]
[ Copyright @Maples7,转载请注明出处。 ]