Find the contiguous subarray within an array (containing at least one number) which has the largest sum.
For example, given the array [−2,1,−3,4,−1,2,1,−5,4],
the contiguous subarray [4,−1,2,1] has the largest sum = 6.
题目描述:最大子段和问题
思路:看到这道题目最简单的思路就是对0 <= i <= j < n的整数进行迭代,对每个整数对程序都要计算x[i..j]的总和,然后检验该综合是否大于迄今为止的最大总和,算法伪码如下:
maxsofar = nums[0]
for i = [0,n)
for j=[i,n)
sum = 0//sum of nums[i..j]
for k = [i,j]
sum+=nums[k]
maxsofar = max(maxsofar, sum)
显然该思路的时间复杂度为
O(n3)
,本题明显会超时,不是可取的算法。
那么如何降低该算法的时间复杂度呢,可以看到sum的计算涉及到了大量的重复计算而sum(nums[i..j])与sum(nums[i..j-1])之间密切相关,因此算法可以进一步改进,其伪码如下:
maxsofar = nums[0]
for i = [0,n)
sum=0
for j=[i,n)
sum+=nums[j]
maxsofar = max(maxsofar, sum)
该算法的时间复杂度为 O(n2) ,但是在leetcode测评仍然超时通不过。降低重复的计算还有一种方法,就是保存中间的计算结果,假设cumarr[i]=sum(nums[0…i]),那么sum(nums[i…j]) = cumarr[j] - cumarr[i-1],因此事先计算好cumarr[i],同样可以将算法的时间复杂度降低为 O(n2) ,其伪码如下:
cumarr[-1]=0
for i=[0,n)
cumarr[i] = cumarr[i-1] + nums[i]
maxsofar = nums[0]
for i = [0,n)
for j=[i,n)
sum=cumarr[j]-cumarr[i-1]
maxsofar = max(maxsofar, sum)
上面两种改进的算法只能将算法的时间复杂度降为 O(n2) ,那么还有时间复杂度更低的算法么?答案显然是有。可以采用分治法,将整个数组划分成左右两个部分,最大子段和要么在左边要么在右边,要么在两者中间,代码如下:
class Solution {
public:
int maxsum(vector<int>& nums, int l, int u){
if(l > u)
return 0;
if(l == u)
return nums[l];
int m = (l + u) / 2;
int lmax, sum, rmax;
lmax = nums[m];
sum = 0;
for(int i = m; i >= l; --i){
sum += nums[i];
lmax = max(lmax, sum);
}
rmax = nums[m+1];
sum = 0;
for(int i = m + 1; i <= u; ++i){
sum += nums[i];
rmax = max(rmax, sum);
}
return max(rmax+lmax, max(maxsum(nums, l, m), maxsum(nums, m+1, u)));
}
int maxSubArray(vector<int>& nums){
maxsum(nums, 0, nums.size()-1);
}
};
该代码通过了leetcode的测评,由于总共进行了
O(logn)
层递归,在每一层递归均进行了
O(n)
次操作,因此算法的时间复杂度为
O(nlogn)
。通过查阅相关资料,本题还有时间复杂度为
O(n)
的算法。算法的原理是:前i个元素中,最大子段和要么在前i-1个元素中(存储在maxsofa),要么其结束位置为i(存储在maxendinghere)。
假设我们已经解决了nums[0..i-1]的最大子段和问题,那么利用上述原理将其扩展到nums[0..i]上:
maxsofar是nums[0..i-1]的最大子段和,那么nums[0…i]的最大子段和要么为maxsofa要么为maxendinghere(结束位置为i);对于结束位置为i-1的maxendinghere,如果maxendinghere>0,那么maxendinghere=maxendinghere+nums[i],如果maxendinghere<=0,那么maxendinghere=nums[i]。
最后maxsofar = max(maxsofar, maxendinghere),即可求出nums[0…i]的最大子段和。代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int size = nums.size();
int maxsofar = nums[0];
int maxendinghere = 0;
for(int i = 0; i < size; ++i){
maxendinghere = maxendinghere > 0 ? maxendinghere+nums[i] : nums[i];
maxsofar = max(maxsofar, maxendinghere);
}
return maxsofar;
}
};