今天在 DSA 课上提到了这道题,我就搜来做了一下。
题目描述
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
暴力枚举什么的思路就不提了,肯定超时了。
思路1:DP
这个题看到我的第一想法就是用 DP,用 f[j] 表示以 j 结尾的连续子序列的最大和,状态转移方程就是 \(f[j] = max(f[j-1]+nums[j],nums[j])\),然后最后再取所有 f[j] 的最大值即得到答案。
class Solution {
public:
int f[300010];
int maxSubArray(vector<int>& nums) {
int n = nums.size();
f[0] = nums[0];
for(int i = 1; i < n; i ++){
f[i] = max(f[i-1]+nums[i],nums[i]);
}
int maxSum = f[0];
for(int i = 1;i < n; i ++)maxSum = max(maxSum,f[i]);
return maxSum;
}
};
可以看出 f[j] 只和 f[j-1] 有关系,因此其实不用开一个数组来存,只要用一个变量记录以当前位置结尾的最大子序列的和以及到目前为止的所有子序列的最大和即可。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int curSum = nums[0];
int maxSum = nums[0];
for(int i = 1; i < n; i ++){
curSum = max(curSum+nums[i],nums[i]);
maxSum = max(maxSum,curSum);
}
return maxSum;
}
};
显然这种解法是 \(O(n)\) 的。
思路2:分治
考虑分治的思考方式:
- 分解:可以将问题分解为求解三个子问题:左半区间的最大子序和,右半区间的最大子序和,横跨中点的最大子序和。
- 解决:左右半区间的子问题可以递归求解,需要解决的只有横跨中点的最大子序和。
要求横跨中点的最大子序和,只需要求左区间以中点为右端点的最大子序和和右区间以中点右边第一个点为左端点的最大子序和相加即可。 - 合并:对三个子问题的解求 max 即可。
class Solution {
public int findMaxSum(int[] nums,int l,int r){
if(l == r)return nums[l];
int mid = l+r>>1;
int leftMaxSum = findMaxSum(nums,l,mid);
int rightMaxSum = findMaxSum(nums,mid+1,r);
int lSum = 0;
int lMaxSum = nums[mid];
for(int t = mid; t >= l; t--){
lSum += nums[t];
lMaxSum = Math.max(lSum,lMaxSum);
}
int rSum = 0;
int rMaxSum = nums[mid+1];
for(int t = mid+1; t <= r; t++){
rSum += nums[t];
rMaxSum = Math.max(rMaxSum,rSum);
}
int maxCrossSum = lMaxSum + rMaxSum;
return Math.max(maxCrossSum,Math.max(leftMaxSum,rightMaxSum));
}
public int maxSubArray(int[] nums) {
return findMaxSum(nums,0,nums.length-1);
}
}
时间复杂度分析:可以写出递推式 \(T(n) = 2T(n/2)+\Theta(n)\),由主方法或者递归树的方法都可以解得 \(T(n) = \Theta(nlgn)\)