[算法手记] 动态规划: 子数组问题

前言: 本文将用于记录博主的做题记录

一, 最大子数组和

题目链接 :https://leetcode.cn/problems/maximum-subarray/?envType=study-plan-v2\&envId=top-100-liked

求出数组内最大的子数组和, 由于题目要求子数组必须连续, 所以注意不要把他和子序列问题混为一谈了

对于这类在一段区间的子问题, 我首先想到就使用动态规划来解决.

按照经验来思考, 我们先初始化一个dp表, 假设dpi : 以 i位置为结尾的最大的子数组和,

先按照这样的思路来尝试解决问题

数组元素有正有负,这代表我们每次加入这个最终的子数组中的数,都会对和造成影响,如果是正数,则无脑往里进 即可, 但是如果是负数呢?难道就直接舍弃前面已经累积的和吗,万一后面后面是一个更大的数字,完全足以抵消让你目前加入这个负数带来的代价 还有累增.

为了填满dp表, 我们自然要遍历一次数组,当但是对于我们每次遍历到的数组元素numsi, 我们需要做一个判断, 那就是该不该让numsi 加入到子数组中?有两种选择

    1. 加入, 让numsi 抱大腿, 去在先前的dpi-1: 以numsi-1 为结尾的最大子数组和上做累加,那么dpi = dpi-1 + numsi
    1. 不加入, 舍弃前面的累加结果,自己作为新的子数组开头单干来, 那么此时dpi = numsi 即可

对于以上这两种情况, 并不清楚那种情况是最优的, 所以要取max

下面是使用动态规划的代码

代码参考一(动态规划)

java 复制代码
``````java
class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        if(n == 1) return nums[0];
        int []dp = new int [n+1];
        //dp[i]:以i位置为结尾的最大的子数组之和
        int ret = Integer.MIN_VALUE;
        for(int i = 1;i <= n;i++){
            dp[i] = Math.max(nums[i-1],dp[i-1] + nums[i-1]);
            ret = Math.max(ret,dp[i]);
        }
        return ret;
    }
}

dp表长度为n+1,为了映射到nums中的元素, 所以要使用i-1来成功映射到nums\[\]中的元素

不过这里也分享另外一种空间复杂度更低的解法

代码参考二(贪心)

java 复制代码
class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        if(n == 1) return nums[0];
        int sum = 0;//初始情况下
        int ret = nums[0];
        for(int i = 0;i < n;i++){
            if(sum > 0){//sum对结果起到增益效果
                sum += nums[i];
            }else{
                sum = nums[i];
            }
            ret = Math.max(sum,ret);
        }
        return ret;
    }
}

总体思想不变,但是核心在于遍历中的两个判断, 这里的sum 表示为前面的子数组累加和,

在遍历数组时,当sum > 0 时,前面的累加结果为正, 则无论如何考虑,我们都会让sum 累加当前元素numsi . 这是为什么呢? 如果numsi > 0 还好, 万一numsi 是一个非常小的负数呢? 这也能说加上numi 起到增益吗?注意我在代码中的注释并不是加上numsi 会对结果起到增益,而是让numsi 加上一个前面的sum正数对结果起到增益,

假设, 前面的计算sum = 3 ,当前numsi很小,为 -100,

那就有两种选择

    1. 抱大腿,继承前面的状态, 3 +(-100) = -97
    1. 不继承,自己单干, 作为新的子数组的开头, sum = -100

把两者对比,可以发现sum > 0 时, 还是抱大腿直接累加最优,和nums正负无关

这也是sum <= 0 时. sum = numsi 这个代码的含义: numsi自己单干!

二, 乘积最大的子数组

题目链接 : https://leetcode.cn/problems/maximum-product-subarray/description/

又是子数组问题, 我们尝试使用动态规划,先按照经验,初始化一个dp表,

dpi : 以i位置为结尾的所有子数组中乘积的最大值,按照这个思路来尝试解题

由于numsi 有正有负, 所以上面的方法是不行的, 因为即使前面的状态是最大值,但是乘一个负数或者0就全部作废; 如果前面的状态是一个很小的负数,再乘一个负数就变正了

一个dp表光存储最大值肯定太局限了, 所以我们就引入两个dp表, 一个存子数组乘积最大值,另一个存储子数组乘积最小值

这样一来,就有两种情况了, 对于numsi ,先前的一个只存最大值的dp表只需要和它做一次判断即可, 这里则需要和两个dp表中保存的状态来在做一个判断,

代码参考一(动态规划)

java 复制代码
class Solution {

    public int maxProduct(int[] nums) {

        int n = nums.length;

        if(n == 1) return nums[0];

        int []dp1 = new int [n+1];//大

        int []dp2 = new int [n+1];//小

        int ret = Integer.MIN_VALUE;

        for(int i = 1;i <= n;i++){

            int x = nums[i-1];

            int y = x * dp1[i-1];//情况1:乘前面保存的状态最大值

            int z = x * dp2[i-1];//情况2:乘前面保存状态的最小值

            dp1[i] = Math.max(x,Math.max(y,z));//MAX维护dp1的最大值

            dp2[i] = Math.min(x,Math.min(y,z));//MIN维护dp2的最小值

            ret = Math.max(dp1[i],ret);//从dp1表中的最大值取舍

        }

        return ret;

    }

}

我们会发现,dpi 的更新,只会依赖dpi-1 前一个状态的值, 先前位置保存的状态都用不到, 所以我们可以来使用一个滚动变量来做空间优化

代码参考二(空间优化)

java 复制代码
class Solution {
    public int maxProduct(int[] nums) {
        int n = nums.length;
        if (n == 1) return nums[0];

        // 用三个变量代替原本的 dp 数组
        int maxVal = nums[0]; // 最大乘积
        int minVal = nums[0]; // 最小乘积
        int ret = nums[0];    // 全局最大值

        for (int i = 1; i < n; i++) {
            int x = nums[i];

            // 三种可能的结果
            int a = x;
            int b = maxVal * x;
            int c = minVal * x;
            maxVal = Math.max(a, Math.max(b, c));
            minVal = Math.min(a, Math.min(b, c));

            // 更新全局最大值
            ret = Math.max(ret, maxVal);
        }

        return ret;
    }
}

以上就是有关子数组问题的解题思路,如有纰漏还请指出~~

相关推荐
薇茗1 小时前
【小编的精选算法题库】
算法·精选算法题库
KaMeidebaby9 小时前
卡梅德生物技术快报|PD1 单克隆抗体定制配套 N 糖全谱质控开发
前端·人工智能·算法·数据挖掘·数据分析
8Qi89 小时前
LeetCode 235. 二叉搜索树的最近公共祖先(LCA)
算法·leetcode·二叉树·递归·二叉搜索树·lca·迭代
bIo7lyA8v10 小时前
算法稳定性分析中的随机扰动建模的技术8
算法
科研online10 小时前
基于多源数据和XGBoost-SHAP分析中国大陆绿地碳汇空间变异影响因素的非线性相关性与尺度差异
算法·学习方法
Cthy_hy10 小时前
拓扑排序超详解:原理 + Kahn 贪心算法
python·算法·贪心算法
三品吉他手会点灯11 小时前
C语言学习笔记 - 43.运算符与表达式 - 运算符1 - 运算符的分类和简单介绍
c语言·笔记·学习·算法
VkN2X2X4b11 小时前
算法复杂度的实验验证与误差分析的技术8
算法
其利天下技术11 小时前
风扇灯无刷电机自适应算法实战指南
算法·cocos2d·无刷电机自适应算法·bldc驱动自适应算法·其利无刷电机驱动算法