最大子数组合-动态规划

74 阅读5分钟

最大子数组合

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。 示例 1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入: nums = [1]
输出: 1

示例 3:

输入: nums = [5,4,-1,7,8]
输出: 23

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

对于此类题目,使用于动态规划。先来讲讲动态规划的解题步骤。

动态规划

  1. 如何判断一个题目是否可以使用动态规划解决?
  2. 对于动态规划问题该从何处入手,具体的解题步骤?

问题判断

一般来说,题目中如果含有最优子结构、重叠子问题、无后效性;那么这就适合用动态规划来求解。不过一般我们很难从问题的描述中直接提取这些特性,因此我们可以放宽条件,先观察问题是否适合使用回溯解决
而回溯一般满足于决策树模型,可以用树来表达,树的节点就是一个决策,而每一条路径就代表一个决策序列。换句话说,如果一个问题包含明确的决策概念,而且解是通过一系列决策产生的,那么他就满足决策树模型,可以用回溯来解决。
‘加分项’

  • 问题中包含了最大(小)或最多(少)等最优描述
  • 问题的状态可以用数组、多维矩阵、树来描述,并且自身与周围的状态存在递推关系
    ‘减分项’
  • 问题注重的是解决问题的方案,而不是最优解。
  • 问题有明显的排列组合特征,返回的是多种解决方案。

问题解题步骤

我从代码随想录中学到的dp五部曲;

  1. dp[i]的含义
  2. 递归公式
  3. dp初始化
  4. 遍历顺序
  5. 打印数组

我认为前三步最为重要,只有明白了dp所代表的含义,才能进行下一步操作;将不确定的因素确定下来,进而把子问题描述清楚,把子问题描述的简单。
其次是递归公式,这一步是解题的关键,也是【状态转移方程】。只有将子问题之间的联系描述清楚了才能完成递归公式,这一步也是最难的。第三步就是思考初始值。

以这个题目为例:最大子数组合
找出一个具有最大和的连续子数组; '最大和且连续' 状态用的是数组,很符合动态规划来解决问题
这是一道典型的使用「动态规划」解决的问题,需要我们掌握动态规划问题设计状态的技巧(无后效性),并且需要知道如何推导状态转移方程,最后再去优化空间。
关键步骤
1.理解题意
题目要我们找出最大的连续子数组,这个连续很重要,而不是子序列,同时要求返回的是结果,而不是求最大的子数组是哪一个,这个就是无后效性
2.如何定义子问题(定义状态)
刚开始做题的时候有点无从下笔的感觉,因为太多种可能了,没有头绪。后来慢慢想,我们要求的是最大子数组,我们不知道最大连续子数组一定会选取哪个数? 但是可以换条思路:可以求出所有经过这个子数组最大和。
例如:
nums = [5,4,-1,7,8]
经过5的最大连续子数组是什么?
经过4的最大连续子数组是什么?
·····
但是你会发现,这些问题之间的联系好像不明确,这是因为 子问题的描述还有不确定的地方 也就是无后效性,什么是无后效性?就是当前状态只与现在有关,与过去的所有状态都不搭嘎。 如果之前的阶段求解的子问题的结果包含了一些不确定的信息,导致了后面的阶段求解的子问题无法得到,或者很难得到,这叫「有后效性」,我们在当前这个问题第 1 次拆分的子问题就是「有后效性」的


经过5,那么这个5是子数组中的哪一个数呢?不确定,反过来如果要无后效性,是不是可以变成子数组的最后一个数呢?这样就不会存在不确定了。
nums = [5,4,-1,7,8]
以5为最后一位的子数组的最大和是多少?
以4为最后一位的子数组的最大和是多少?
······

这样一看,是不是两个之间是有联系了? 经过某个数且这个数且在最后一位。
那么我们就可以开始第二步,递归公式(状态转移方程)。
因为-104 <= nums[i] <= 104;有负有正,自身关系与周围有着递进关系,这个时候就需要分类讨论了; 先假设:4是最后一位数,他的前一位是5;如果>0,就可以加入到以4结尾的子数组中;但如果<=0,这就没必要加入进来了,这样以4为结尾的子数组反而会更小;
这样我们就可以推导出:

image.png

递推公式有了:dp[i] = Math.max(dp[i-1] + nums[i],nums[i])
接下来就是初始化dp;
因为最大连续子数组,那么肯定最初就只有第一位: dp[0] = nums[0]
接着就是遍历顺序

public class Solution { 
        public int maxSubArray(int[] nums) {
            int[] dp = new int[nums.length]; 
            int dp[0] = nums[0]; 
            int max = nums[0];
            for (int i=1 ; i<nums.length;i++) {
                  dp[i] =  Math.max(dp[i-1] + nums[i],nums[i]);
                  max = Math.max(max,dp[i]);
                } 
             return max; 
           } 
       } 

时间复杂度:O(N) ,这里 N 是输入数组的长度。

优化空间后的代码

public class Solution { 
        public int maxSubArray(int[] nums) {
            int pre =0; 
            int res = nums[0]; 
            for (int num : nums) {
                pre = Math.max(pre + num, num);
                res = Math.max(res, pre); 
                } 
             return res; 
           } 
       } 

时间复杂度:O(N) ,这里 N 是输入数组的长度。