题目描述:
解题思路:
可以使用动态规划或者分治算法
动态规划
对于求最大自序和,我们可以使用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; } }