题目来源:《剑指offer》面试题31、《编程之美》2.14
题目:输入一个整形数组,数组里有正数也有负数。数组中一个或连续多个整数组成一个子数组。求所有子数组的和的最大值
解法一:假设id代表自序列的一个起点,j代表终点。如果a[i]是负的,那么它不可能代表最优子序列的起点,因为任何包含a[i]的作为起点的子序列都可以通过a[i+1]作起点得到改进。类似地,任何负的子序列不可能是最优子序列的前缀。比如有数组{1,-2,3,10,-4,7,2,-5}。我们试着从头道尾逐个累加数组中的每个数字。初始化和为0。第一步加上第一个数字1,此时和为1.接下来第二步加上数字-2,和就变成了了-1.第三步加上数字3.我们注意到由于此前累计的和是-1,小于0,那如果用-1加上3,得到的和是2,比3本身还小。也就是说从第一个数字开始的子数组的和会小于从第三个数字开始的子数组的和。因此我们不用考虑从第一个数字开始的子数组,之前累加的和也就被抛弃。
时间复杂度O(n)
bool g_invalidinput = false; int GetMaxSum(int numbers, int len) { if (numbers == NULL || len <= 0) { g_invalidinput = true; return 0; } g_invalidinput = false; int max_sum = INT_MIN; int current_sum = 0; for (int i = 0; i < len; i++) { if (current_sum <= 0) { current_sum = numbers[i]; } else { current_sum += numbers[i]; } if (current_sum > max_sum) max_sum = current_sum; } return max_sum; }
解法二:分治方法。假定我们要寻找子数组A[low,high]的最大子数组。使用分治技术意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组A[low..mid]和A[mid+1..high]。A[low..high]的在任何连续子数组A[i..j]所处的位置必然是以下三种情况之一:
1. 完全位于子数组A[low..mid]中,因此low<=i<=j<=mid
2. 完全位于子数组A[mid+1..high]中,因此mid<i<=j<=high
3. 跨越了中点,因此low<=i<=mid<j<=high。
因此,A[low..high]的一个最大子数组所处的位置必然是这三种情况之一。实际上,A[low..high]的一个最大子数组必然是完全位于A[low..mid]中、完全位于A[mid+1..high]中或者跨越了中点的所有子数组中和最大者。我们可以递归地求解A[low..mid]和A[mid+1..high]的最大子数组,因为这两个自问题仍然是最大子数组问题,只是规模更小。因此,剩下的全部工作就是寻找跨越中点的最大子数组,然后在这三者中选取和最大者。
时间复杂度O(nlgn)
bool g_invalid_input = false; int MaxSubArray(int numbers, int len) { if (numbers == NULL || len <= 0) { g_invalid_input = true; return 0; } g_invalid_input = false; return MaxSubArray(numbers, 0, len - 1); } int MaxSubArray(int *numbers, int start, int end) { if (start == end) { return a[start]; } int mid = start + (end - start) / 2; int left_max_subarray = MaxSubArray(numbers, start, mid); int right_max_subarray = MaxSubArray(numbers, mid + 1, end); int crossing_subarray = INT_MIN; int left_sum = INT_MIN; int current_sum = INT_MIN; for (int i = mid; i >= start; i--) { current_sum += numbers[i]; if (current_sum > left_sum) left_sum = current_sum; } int right_sum = INT_MIN; current_sum = INT_MIN; for(int i = mid + 1; i <= end; i++) { current_sum += numbers[i]; if (current_sum > right_sum) right_sum = current_sum; } int crossing_sum = left_sum + right_sum; if (left_max_subarray >= crossing_sum && left_max_subarray >= right_max_subarray) return left_max_subarray; else if (right_max_subarray >= crossing_sum && right_max_subarray >= crossing_sum) return right_max_subarray else return crossing_sum; }
参考资料:
1. 《剑指offer》 电子工业出版社
2. 《编程之美》 电子工业出版社
3. 《算法导论》4.1 最大子数组问题 机械工业出版社
4. 《数据结构与算法分析 C++描述》(第三版)P42 人民邮电出版社
原文:http://www.cnblogs.com/vincently/p/4780538.html