53. 最大子数组和

题目链接:力扣

解题思路:动态规划,对于动态规划的解题思路,一般分为下面三步:

  1.  定义状态(定义子问题):找出子问题抽象定义,也就是拆分子问题,其中状态可以简单的理解为子问题的答案
    1. 这道题不容易直接求解出最终的答案,可以换个思路,求解以num[i]结尾的连续子数组的最大和为多少,
    2. 所以状态可以定义为:dp[i],表示以nums[i]结尾的连续子数组的最大和
  2. 确定状态转移方程:找出状态与状态之间的递推关系
    1. 根据状态的定义,以nums[i]结尾的最大连续子数组的和与dp[i-1]相关,有两种情况,
      1. 将nums[i]拼接在以nums[i-1]结尾的最大连续子数组上,构成和更大的连续子数组,dp[i]=dp[i-1]+nums[i]
      2. nums[i]单独作为一个连续子数组,dp[i]=nums[i]
    2. 目标是让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]
  3. 初始状态和边界情况:最简单的子问题的解
    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;
    }
}

分治解法:类似于线段树

  1. 对于区间nums[left,...,right],可以分解为两个子区间,nums[Left,...,mid],nums[mid+1,right],对于每个区间维持四个状态变量,保存当前区间的一些信息:
    1. lSum:[left,rigt]区间内以left为左端点的最大连续数组和
    2. rSum:[left,rigt]区间内以right为右端点的最大连续数组和
    3. mSum:[left,rigt]区间内以的最大连续数组和(最终的目标)
    4. iSum:[left,rigt]区间内以的区间和
  2. 当求解[left,right]区间的四个状态变量时,需要[left,mid],[mid+1,right]这两个区间的状态变量。假设[left,mid]左半部分的状态变量记为 l , [mid+1,right]右半部分的状态变量记为r,则:
    1. 当区间长度等于1时,分治结束,区间的四个状态变量都等于nums[left]
    2. 当区间长度大于1时,则当前区间[left,rigt]的四个状态变量的更新如下:
      1. iSum:等于[left,mid]区间的iSum加上[mid+1,right]区间的iSum,iSum=l.iSum+r.iSum
      2. lSum:等于[left,mid]区间的lSum 或者 [left,mid]区间的iSum加上[mid+1,right]区间的lSum,两者取较大的那个,lSum = Math.max(l.lSum, l.iSum + r.lSum)
      3. rSum:等于[mid+1,right]区间的rSum 或者 [mid+1,right]区间的iSum加上[left,mid]区间的rSum,两者取较大的那个,rSum = Math.max(r.rSum, r.iSum + l.rSum)
      4. mSum:
        1. 如果最大连续数组没有跨越两个区间,则 mSum等于 [left,mid]区间的mSum或者[mid+1,right]区间的mSum,两者取较大的那个
        2. 如果跨越了两个区间,则nums[mid]和nums[mid+1]一定是包含的,则mSum等于 [left,mid]区间的rSum加上[mid+1,right]区间的lSum
        3. 综上,mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum)
  3. 最后只需要返回[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);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值