方法一:DP
以前做OJ的时候都想不通为什么是DP,现在终于搞明白了。
一开始想的时候,想着 dp[i] 为下标0~i元素最大的字串和。
dp[i] = max { dp[i-1] a[i]不在subarry中
dp[i-1]+ a[i] 在subarray中 承接之前的subarray
a[i] } 在subarray中 新开一个subarray
这个状态转移方程是有问题的,因为没法确定dp[i-1]是否在subarray中,所以dp[i-1]+ a[i]这里是不对的。
因此直接一步到位解这个问题是不行的。根据上述分析,可以把问题拆分成两部分:
f[i] 表示以a[i]结尾的最大子串和,dp[i] 表示下标0~i元素所包括的最大子串和。目标则是dp[n-1]。
f[0]=a[0], dp[0]=a[0];
f[i] = max{ f[i-1]+a[i] 承接之前的subarray
a[i] } 新开一个subarray
dp[i] = max{ dp[i-1] subarray不包括a[i]
f[i] } subarray包括a[i]
实际上就是把最开始的思路分成了两步。由于f[i]和dp[i]的求解的过程中都只依赖上一个状态的元素,因此空间复杂度可以从O(n)降到O(1)。
class Solution { public: int maxSubArray(vector<int>& nums) { int max_so_far=nums[0]; int max_ending_here=nums[0]; for (int i=1;i<nums.size();++i){ max_ending_here = max(max_ending_here+nums[i], nums[i]); max_so_far = max(max_so_far, max_ending_here); } return max_so_far; } };
方法二:Divide and Conquer
算法导论里divide and conquer就以maximum subarray为例。思路也比较简单,数组可以分成两部分,最大字串和要么在两边,要么在中间。
T(n) = 2T(n/2)+O(n),因此时间复杂度O(nlogn)
class Solution { public: int maxSubArray(vector<int>& nums) { return maxSubArray(nums, 0, nums.size()-1); } int maxSubArray(vector<int> &nums, int low, int high){ if (low==high) return nums[low]; int mid=(low+high)/2; int leftMax=maxSubArray(nums,low,mid); int rightMax=maxSubArray(nums,mid+1,high); int leftSum=INT_MIN; int sum=0; for (int i=mid;i>=low;--i){ sum += nums[i]; leftSum = max(leftSum, sum); } int rightSum=INT_MIN; sum = 0; for (int i=mid+1;i<=high;++i){ sum += nums[i]; rightSum = max(rightSum, sum); } int crossingMax=leftSum+rightSum; return max(crossingMax, max(leftMax,rightMax)); } };