题目解析:求出一个含负数的数组中和最大的子串,输出这个最大的和。
解法1——动态规划
以nums=[-2,1,-3,4,-1,2,1]为例,考虑动态规划的求解:
递推方程为:sum[i] = max{ nums[i], nums[i] + sums[i+1] } 表示为从第i个元素开始的最大子串和。
即:
nums | -2 | 1 | -3 | 4 | -1 | 2 | 1 |
sum(从后向前计算) | 2 | 4 | 3 | 6 | 2 | 3 | 1 |
代码实现为:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int sum, maxSum;
sum = maxSum = nums[n-1];
for(int i=n-2;i>=0;--i){
sum = max(nums[i],nums[i]+sum);
maxSum = max(maxSum,sum);
}
return maxSum;
}
};
动态规划的时间复杂度为O(n)。耗时11ms。
解法2:来源于http://blog.youkuaiyun.com/joylnwang/article/details/6859677,采用卡耐基梅隆大学Jay Kadane给出的一个线性时间算法。该算法的思想是:对一个数组array[1...n]求其最大子串和,有两个结论:
1)对于array[1...n],如果array[i...j]就是满足和最大的子串,那么对于任何k(i<=k<=j),我们有array[i...k]的和大于0。因为如果存在k使得array[i...k]的和小于0,那么我们就有array[k+1...j]的和大于array[i...j],这与我们假设的array[i...j]就是array中和最大子串矛盾。
2)其次,我们可以将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,且对于所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大于0。此时我们要说明的是,满足条件的和最大子串,只能是上述某个子串的前缀,而不可能跨越多个子串。我们假设array[p...q],是array的和最大子串,且array[p...q],跨越了array[i...j],array[j+1...k]。根据我们的分组方式,存在i<=m<j使得array[i...m]的和是array[i...j]中的最大值,存在j+1<=n<k使得array[j+1...n]的和是array[j+1...k]的最大值。由于array[m+1...j]使得array[i...j]的和小于0。此时我们可以比较array[i...m]和array[j+1...n],如果array[i...m]的和大于array[j+1...n]则array[i...m]>array[p...q],否array[j+1...n]>array[p...q],无论谁大,我们都可以找到比array[p...q]和更大的子串,这与我们的假设矛盾,所以满足条件的array[p...q]不可能跨越两个子串。对于跨越更多子串的情况,由于各子串的和均为负值,所以同样可以证明存在和更大的非跨越子串的存在。对于单元素和最大的特例,该结论也适用。
由上述算法的结论可以得到本题解答的思路:把整个数组从左到右只要元素(累加)为负则分成一个子串,最后一个子串可正可负,分割成子串的同时求出各个子串的最大前缀和。如果整个数组全部都是负数,只需要找到这些负数中最大的元素即可。代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int sum=0,maxsum;
maxsum = nums[0];
for(int i=0;i<n;++i){
sum += nums[i];
if(maxsum < sum)
maxsum = sum;
if(sum<0)
sum = 0; // 分下一组子串
}
return maxsum;
}
};
时间复杂度也是O(n),但耗时13ms,比动态规划更慢一点。