题目描述:
解题思路:
可以使用动态规划或者分治算法
动态规划
对于求最大自序和,我们可以使用arr[i]来记录以下标 i 结尾的最大子序和,用一维数组来表示;
则有
arr[i] = max(arr[i-1]+arr[i],arr[i])
;即要么该arr[i]单独成一段,要么和前面arr[i-1]共同组成一段。可写出方程
$$
f(i) = max(f(i-1)+arr[i],arr[i])
$$
则可以看出f(i)只和f(i-1)有关,因此可以使用一个变量pre用于记录当前i下的f(i-1),用maxVal记录当前i下的最大子序和。时间复杂度是O(n)
分而治之
可以将该数组划分为左右两个部分,每次都以其中间m为界限划分,[l,m] [m+1,r]
划分成两部分之后,再进行合并,我们要维护四个变量:
- lSum:以左端点的最大子序和
- rSum:以右端点的最大子序和
- mSum:任意端点的最大子序和
- iSum:整个区间的和
在合并时,要维护的这四个变量
对于iSum最好维护,区间 [l,r]的iSum 就等于「左子区间」的iSum 加上「右子区间」的 iSum。
对于 [l,r]的lSum,存在两种可能,它要么等于「左子区间」的lSum,要么等于「左子区间」的iSum 加上「右子区间」的 lSum,二者取大。
rSum和lSum类似
对于mSum;我们可以考虑 [l,r]的 mSum 对应的区间是否跨越 m——它可能不跨越 m,也就是说 [l,r] 的 mSum 可能是「左子区间」的mSum 和 「右子区间」的 mSum 中的一个;它也可能跨越 m,可能是「左子区间」的 }rSum 和 「右子区间」的lSum 求和。三者取大。
代码:
//动态规划
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0;
int maxVal = nums[0];
for(int i = 0;i < nums.length;i++){
pre = Math.max(pre + nums[i],nums[i]);
maxVal = Math.max(maxVal,pre);
}
return maxVal;
}
}
//分而治之:
class Solution {
public class Status{
public int lSum;
public int rSum;
public int mSum;
public int iSum;
public Status(){};
public Status(int lSum,int rSum,int mSum,int iSum){
this.lSum = lSum;
this.rSum = rSum;
this.mSum = mSum;
this.iSum = iSum;
}
}
public Status divide(int l,int r,int[] nums){
if(l == r){
return new Status(nums[l],nums[l],nums[l],nums[l]);
}
int m = (l + r) >> 1;
Status lSub = divide(l,m,nums);
Status rSub = divide(m+1,r,nums);
return merge(lSub,rSub);
}
public Status merge(Status lSub,Status rSub){
int lSum = Math.max(lSub.lSum,lSub.iSum+rSub.lSum);
int rSum = Math.max(rSub.rSum,rSub.iSum+lSub.rSum);
int mSum = Math.max(Math.max(rSub.mSum,lSub.mSum),lSub.rSum + rSub.lSum);
int iSum = lSub.iSum + rSub.iSum;
return new Status(lSum,rSum,mSum,iSum);
}
public int maxSubArray(int[] nums) {
return divide(0,nums.length-1,nums).mSum;
}
}