题目链接:力扣
解题思路:动态规划,对于动态规划的解题思路,一般分为下面三步:
- 定义状态(定义子问题):找出子问题抽象定义,也就是拆分子问题,其中状态可以简单的理解为子问题的答案
- 这道题不容易直接求解出最终的答案,可以换个思路,求解以num[i]结尾的连续子数组的最大和为多少,
- 所以状态可以定义为:dp[i],表示以nums[i]结尾的连续子数组的最大和
- 确定状态转移方程:找出状态与状态之间的递推关系
- 根据状态的定义,以nums[i]结尾的最大连续子数组的和与dp[i-1]相关,有两种情况,
- 将nums[i]拼接在以nums[i-1]结尾的最大连续子数组上,构成和更大的连续子数组,dp[i]=dp[i-1]+nums[i]
- nums[i]单独作为一个连续子数组,dp[i]=nums[i]
- 目标是让dp[i]尽可能地大,很容易想到的情况,两个数相加a+b,如果一种一个数a是负数,显然a+b的结果肯定要比b更小。所以状态转移就很清楚了,如果dp[i-1]>0,就令dp[i]=dp[i-1]+nums[i],否则,dp[i]=nums[i],两种情况可以简写为dp[i]=Math.max(dp[i-1],0) + nums[i]
- 根据状态的定义,以nums[i]结尾的最大连续子数组的和与dp[i-1]相关,有两种情况,
- 初始状态和边界情况:最简单的子问题的解
- 根据状态的定义,dp[0]=nums[0]
AC代码:
class Solution {
public static int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
dp[0]=nums[0];
int result = dp[0];
for (int i =1;i<nums.length;i++){
dp[i]=Math.max(dp[i-1],0)+nums[i];
result = Math.max(result,dp[i]);
}
return result;
}
}
优化:dp[i]的值之和dp[i-1]相关,所以可以使用一个滚动变量endmax保存dp[i-1]的结果或者0
AC代码
class Solution {
public static int maxSubArray(int[] nums) {
int endMax = 0, result = nums[0];
for (int num : nums) {
endMax = Math.max(endMax, 0) + num;
result = Math.max(endMax, result);
}
return result;
}
}
分治解法:类似于线段树
- 对于区间nums[left,...,right],可以分解为两个子区间,nums[Left,...,mid],nums[mid+1,right],对于每个区间维持四个状态变量,保存当前区间的一些信息:
- lSum:[left,rigt]区间内以left为左端点的最大连续数组和
- rSum:[left,rigt]区间内以right为右端点的最大连续数组和
- mSum:[left,rigt]区间内以的最大连续数组和(最终的目标)
- iSum:[left,rigt]区间内以的区间和
- 当求解[left,right]区间的四个状态变量时,需要[left,mid],[mid+1,right]这两个区间的状态变量。假设[left,mid]左半部分的状态变量记为 l , [mid+1,right]右半部分的状态变量记为r,则:
- 当区间长度等于1时,分治结束,区间的四个状态变量都等于nums[left]
- 当区间长度大于1时,则当前区间[left,rigt]的四个状态变量的更新如下:
- iSum:等于[left,mid]区间的iSum加上[mid+1,right]区间的iSum,iSum=l.iSum+r.iSum
- lSum:等于[left,mid]区间的lSum 或者 [left,mid]区间的iSum加上[mid+1,right]区间的lSum,两者取较大的那个,lSum = Math.max(l.lSum, l.iSum + r.lSum)
- rSum:等于[mid+1,right]区间的rSum 或者 [mid+1,right]区间的iSum加上[left,mid]区间的rSum,两者取较大的那个,rSum = Math.max(r.rSum, r.iSum + l.rSum)
- mSum:
- 如果最大连续数组没有跨越两个区间,则 mSum等于 [left,mid]区间的mSum或者[mid+1,right]区间的mSum,两者取较大的那个
- 如果跨越了两个区间,则nums[mid]和nums[mid+1]一定是包含的,则mSum等于 [left,mid]区间的rSum加上[mid+1,right]区间的lSum
- 综上,mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum)
- 最后只需要返回[0,nums.length-1]区间的mSum即可
AC代码
class Solution {
//用于保存区间[l,r]相关的信息
public class Status {
//[l,r]内以l为左端点的最大字段和
public int lSum;
//[l,r]内以r为右端的最大字段和
public int rSum;
//[l,r]内的最大字段和
public int mSum;
//[l,r]的区间和
public int iSum;
public Status(int lSum, int rSum, int mSum, int iSum) {
this.lSum = lSum;
this.rSum = rSum;
this.mSum = mSum;
this.iSum = iSum;
}
}
public int maxSubArray(int[] nums) {
return getMaxSub(nums, 0, nums.length - 1).mSum;
}
public Status getMaxSub(int[] nums, int l, int r) {
if (l == r) {
return new Status(nums[l], nums[l], nums[l], nums[l]);
}
int mid = (l + r) >> 1;
//得到左区间的状态信息
Status lStatus = getMaxSub(nums, l, mid);
//得到右区间的状态信息
Status rStatus = getMaxSub(nums, mid + 1, r);
//根据左右区间的状态信息,得到当前区间的状态信息
return pushUp(lStatus, rStatus);
}
//合并节点信息,返回合并后的状态
public Status pushUp(Status l, Status r) {
int iSum = l.iSum + r.iSum;
int lSum = Math.max(l.lSum, l.iSum + r.lSum);
int rSum = Math.max(r.rSum, r.iSum + l.rSum);
int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);
return new Status(lSum,rSum,mSum,iSum);
}
}