算法沉淀——动态规划篇(子数组系列问题(上))

算法沉淀——动态规划篇(子数组系列问题(上))

  • 前言
  • 一、最大子数组和
  • 二、环形子数组的最大和
  • 三、乘积最大子数组
  • 四、乘积为正数的最长子数组长度

前言

几乎所有的动态规划问题大致可分为以下5个步骤,后续所有问题分析都将基于此

  • 1.、状态表示:通常状态表示分为以下两种,其中更是第一种为主。

    • 以i为结尾,dp[i] 表示什么,通常为代求问题(具体依题目而定)
    • 以i为开始,dp[i]表示什么,通常为代求问题(具体依题目而定)
  • 2、状态转移方程
    *以上述的dp[i]意义为更具, 通过最近一步来分析和划分问题,由此来得到一个有关dp[i]的状态转移方程。

  • 3、dp表创建,初始化

    • 动态规划问题中,如果直接使用状态转移方程通常会伴随着越界访问等风险,所以一般需要初始化。而初始化最重要的两个注意事项便是:保证后续结果正确,不受初始值影响;下标的映射关系
    • 初始化一般分为以下两种:
      • 直接初始化开头的几个值。
      • 一维空间大小+1,下标从1开始;二维增加一行/一列
  • 4、填dp表、填表顺序:根据状态转移方程来确定填表顺序。

  • 5、确定返回值

一、最大子数组和

【题目链接】:53. 最大子数组和
【题目】:

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

【示例】:

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

【分析】:
 我们可以定义dp[i]表示以i为结尾,最大连续子数组和。此时连续子数组长度分为1和大于1,分别对应如下情况:

  1. 如果dp[i-1]大于0,此时最大连续子数组和为dp[i-1] + nums[i]。长度大于1。
  2. 如果dp[i-1] <= 0,此时最大的连续子数组和就是nums[i]本身。长度为1.

 所以我们可以得到状态转移方程为:dp[i] = max(nums[i - 1], dp[i - 1] + nums[i - 1])

 但显然当i为0时,状态转移方程不适用。这里我们给出的方法时dp表空间额外增加1。同时为了保证在使用状态转移方程,新增空间对后续填表结果不产生印象。我们将dp[0]初始化为0即可。

【代码编写】:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n + 1);
        int ret = INT_MIN;
        for(int i = 1; i <= n; i++)
        {
            dp[i] = max(nums[i - 1], dp[i - 1] + nums[i - 1]);
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

二、环形子数组的最大和

【题目链接】:918. 环形子数组的最大和
【题目】:

 给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 。
 环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。
&emsp子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], …, nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。

【示例】:

输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10
提示:
n == nums.length
1 <= n <= 3 * 104
-3 * 104 <= nums[i] <= 3 * 104

【分析】:
 显然如果我们"死板"按照题目所说的环形数组进行分析,会感到无从下手。所以我们可以换一种思路:我们对数组被偷元素是否包含首尾,进行分类讨论。从而将环形数组问题转化称求普通子数组的最大和问题!

分类情况:

  1. 首位元素都被选,此时我们将原问题转化成求普通数组中子数组的最小和。最后用整个数组的和减去最小和,间接求出该情况最大值。
    在这里插入图片描述

  2. 首位元素不全都被选。此时原问题转化成普通数组中子数组的最大和。
    在这里插入图片描述

如何求普通数组最大子数组和、最小子数组和:

 这里我们可以定义fi]表示以i位置为结尾的子数组最大和,g[i]表示以i位置为结尾的子数组最小和。

状态转移方程推导:

  1. 以i位置为结尾的子数组最大和分两种情况:如果f[i-1] >0, 此时最大子数组和为f[i-1] + nums[i]。否则最大子数组和为nums[i]本身。所以状态转移方程为:f[i] = max(nums[i], dp[i-1] + nums[i])
  2. 同理,以i位置为结尾的子数组最小和分两种情况:如果f[i-1] <0, 此时最小子数组和为f[i-1] + nums[i]。否则最小子数组和为nums[i]本身。所以状态转移方程为:g[i] = min(g[i-1]+ nums[i], nums[i])

细节处理:
 显然不管是求f[i]还是g[i],当i为0时,状态转移方程失效。这里各位办法是给f、g都额外新增一个空间。最后从左往右依次填表即可。
如果数组中元素全为负数时,此时子数组和最大值一定为负数。但此时我们发现g[i]最终求得的子数组最小和就是整个数组和,此时数组和减子数组最小和的最终结果为0。需要单独处理
在这里插入图片描述

【代码编写】:

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        //数组和
        int sum = 0;
        for(auto x : nums)
        {
            sum += x;
        }
        int n = nums.size();
        vector<int> f(n + 1);//普通数组最大连续子数组和
        auto g = f;//普通数组最小连续子数组和
        int fmax = INT_MIN, gmin = INT_MAX;//分别统计f中最大值,g中最小值
        for(int i = 1; i <= n; i++)
        {
            f[i] = max(nums[i - 1], f[i - 1] + nums[i - 1]);
            fmax = max(fmax, f[i]);

            g[i] = min(nums[i - 1], g[i - 1] + nums[i - 1]);
            gmin = min(gmin, g[i]);
        }
        return sum == gmin ? fmax : max(fmax, sum - gmin);//判断数组元素是否全为负数
    }
};

三、乘积最大子数组

【题目链接】:152. 乘积最大子数组
【题目】:

 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
&emsp测试用例的答案是一个 32-位 整数。

【示例】:

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

【分析】:
 我们可以定义f[i]表示以i为结尾的最大子数组乘积,g[i]表示以i为结尾的最小子数组乘积。
状态转移方程推导

  1. 以i为结尾的子数组长度可能为1,或大于1。对于大于1的情况,如果nums[i]>0,此时最大子数组乘积为g[i-1]*nums[i];如果nums[i]<0,此时最大子数组乘积为g[i-1]*nums[i]。我们仅需直接求得3种情况的最大值即可,即状态转移方程为: f[i] = max(nums[i - 1], max(f[i - 1] * nums[i - 1], g[i - 1] * nums[i - 1]))
    在这里插入图片描述

  2. 同理对于g[i]来说,也存在如下几种情况。最终可得状态转移方程为g[i] = min(nums[i - 1], min(g[i - 1] * nums[i - 1], f[i - 1] * nums[i - 1]));

在这里插入图片描述

细节处理:
 当i=0时,f、g的状态转移方程不适用。我们可以为f、g额外增加1个空间。同时为了防止新增结果对后续填表造成影响,我们将g[0]、f[0]初始化为1。

【代码编写】

public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1);//f[i]表示以i为结尾的子数组的最大乘积
        auto g = f;//g[i]表示以i为结尾的子数组的最小乘积
        int ret = INT_MIN;
        f[0] = 1, g[0] = 1;
        for(int i = 1; i <= n; i++)
        {
            f[i] = max(nums[i - 1], max(f[i - 1] * nums[i - 1], g[i - 1] * nums[i - 1]));
            g[i] = min(nums[i - 1], min(g[i - 1] * nums[i - 1], f[i - 1] * nums[i - 1]));
            ret = max(ret, f[i]);
        }
        return ret;
    }
};

四、乘积为正数的最长子数组长度

【题目链接】:1567. 乘积为正数的最长子数组长度
【题目】:

 给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。
 请你返回乘积为正数的最长子数组长度。

【示例】:

输入:nums = [-1,-2,-3,0,1]
输出:2
解释:乘积为正数的最长子数组是 [-1,-2] 或者 [-2,-3] 。

【分析】:
 我们定义f[i]表示以i为结尾,乘积为正的子数组最长长度;g[i]表示以i为结尾,乘积为负的子数组最长长度。

状态转移方程推导:
 我们可以从最后一个元素nums[i[入手,此时分为以下几种情况:

  1. 最长子数组长度可能为1,或大于1,具体如下:

在这里插入图片描述
将上述情况仅需合并后,可得:
在这里插入图片描述

  1. 同理,g[i]的情况如下:
    在这里插入图片描述
    合并后,可得:
    在这里插入图片描述

细节处理:
 显然当i为0时,f、g的状态转移方程都失效。所以我们给f、g额外增加一个空间。为了保证新增空间对后续填表结果不造成印象,我们将f[0]、g[0]初始化为0。最后从左往右填表即可。

【代码编写】:

class Solution {
public:
    int getMaxLen(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1);//f[i]: 以i为结尾的,乘积为正的最长子数组长度
        auto g = f;//g[i]: 以i为结尾的,乘积为负的最长子数组长度
        int ret = INT_MIN;
        for(int i = 1; i <= n; i++)
        {
            if(nums[i - 1] > 0)
            {
                f[i] = f[i - 1] + 1;
                g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;
            }
            else if(nums[i - 1] < 0)
            {
                f[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;
                g[i] = f[i - 1] + 1;
            }
            ret = max(ret, f[i]);
        }
        return ret;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/504322.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Available platform plugins are: linuxfb, minimal, offscreen, vnc.

说明&#xff1a; buildroots根文件中已经移植好了QT的库&#xff0c;但是运行QT交叉编译之后的可执行文件报错&#xff1a; qt.qpa.plugin: Could not find the Qt platform plugin "eglfs" in "" This application failed to start because no Qt platf…

P4995 跳跳!(贪心)

多么痛的领悟&#xff01;大数据要开long long&#xff01;&#xff01;&#xff01;简单longlong就AC&#xff01; 代码1&#xff1a; #include<algorithm> #include<iostream> #include<cstring> #include<queue> #include<cmath> using name…

哪种排序算法在不同情况下性能最好?

哪种排序算法在不同情况下性能最好&#xff1f;&#x1f50d;&#x1f4ca; 哪种排序算法在不同情况下性能最好&#xff1f;&#x1f50d;&#x1f4ca;&#x1f4dd; 摘要&#x1f680; 引言&#x1f4cb; 正文内容&#xff08;详细介绍&#xff09;冒泡排序快速排序&#x1f…

基于ssm旅游资源网站(java项目+文档+源码)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的旅游资源网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 旅游资源网站的主要使用者分为管理…

RK3588平台开发系列讲解(I2C开发篇)

目录 前言 I2C 流程 Trasmint only mode(I2C_CON[1:0]2’b00) Mix mode (I2C_CON[1:0]2’b01 or I2C_CON[1:0]2’b11) Receive only mode (I2C_CON[1:0]2’b10) I2C 驱动参数配置 I2C 使用 1 Kernel space 2User space GPIO 模拟 I2C I2C 常见问题 前言 ROCKCHIP 系…

四、戴尔R630本地服务器Linux Centos7.9系统安装docker-ce-20.10.10-3.el7版本

如需查阅上一步骤,请点击下面链接:3、Linux Centos7.9系统的iDRAC远程管理、网络设置、SecureCRT远程登录终端、企业级静态ip地址配置-CSDN博客文章浏览阅读898次,点赞17次,收藏15次。iDRAC连接登录服务器远程管理。https://blog.csdn.net/weixin_67440240/article/details…

【Anaconda】Linux下Anaconda安装和虚拟环境配置

Linux下Anaconda安装和虚拟环境配置 一、安装anaconda二、conda虚拟环境管理三、jupyter相关启动部署四、遇到问题 下面介绍整体流程&#xff0c;遇到问题优先看“遇到问题章节”&#xff01; 一、安装anaconda 1.下载anaconda安装包 &#xff08;1&#xff09;可以选择在官网…

linux i2c-tools使用总结

1,安装iic-tools sudo apt install i2c-tools -y 2,查看有几条iic总线 [root@sino-platform:/root]# ls /dev/i2c- i2c-0 i2c-1 i2c-2 i2c-3 3,查看i2c命令 [root@sino-platform:/root]# i2c i2cdetect i2cdump i2cget i2cset 4,iic-help命令 [root@sino-platform…

postcss安装和使用(详细)

1,安装postcss&#xff1a; 在此之前需要安装有node.js 第一步 命令&#xff1a;cnpm install postcss-cli -g 第二步 命令&#xff1a;cnpm install postcss –g 推荐内容 2,下载autoprefixer插件&#xff0c;并创建postcss.config.js文件并写入配置代码 autoprefixer插件…

信息素养和社会责任

1.信息素养&#xff1a; 信息素养是一种了解、收集、评估、和利用信息的知识结构能力。 信息素养的四个要素&#xff1a;信息意识&#xff0c;信息知识&#xff0c;信息能力&#xff0c;信息道德 信息意识是先导&#xff08;前提&#xff09;&#xff0c;是对关键信息具有持…

大学教材《C语言程序设计》(浙大版)课后习题解析 | 第七、八章

概述 本文主要提供《C语言程序设计》(浙大版) 第七、八章的课后习题解析&#xff0c;以方便同学们完成题目后作为参考对照。后续将更新第九、十章节的课后习题解析&#xff0c;如想了解更多&#xff0c;请持续关注该专栏。 专栏直达链接&#xff1a;《C语言程序设计》(浙大版)…

【OceanBase实战之路】第3篇:多租户架构实现资源隔离

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 一、什么是OceanBase的多租户二、兼容模式2.1 MySQL 模式2.2 Oracle 模式三、租户介绍3.1 系统租户3.2 用户租户3.3 Meta 租…

STLExporter 是用于将 Three.js 场景中的几何体数据导出为 STL 格式的类。

demo案例 STLExporter 是用于将 Three.js 场景中的几何体数据导出为 STL 格式的类。下面是关于 STLExporter 的入参、出参、方法和属性的讲解&#xff1a; 入参 (Parameters): scene: THREE.Scene 类型的参数&#xff0c;表示要导出为 STL 格式的 Three.js 场景对象。 出参…

Python API(happybase)操作Hbase案例

一、Windows下安装Python库&#xff1a;happybase pip install happybase -i https://pypi.tuna.tsinghua.edu.cn/simple 二、 开启HBase的Thrift服务 想要使用Python API连接HBase&#xff0c;需要开启HBase的Thrift服务。所以&#xff0c;在Linux服务器上&#xff0c;执行如…

2024/3/31 蜂鸣器(1)

蜂鸣器分为有源蜂鸣器和无源蜂鸣器。有源内部自带振荡源&#xff0c;接上直流电压即可发声。无源则需要控制器提供振荡脉冲才可发声&#xff0c;调整提供振荡脉冲频率可以发出不同频率的声音。&#xff08;好想用stm32做个电子琴&#xff09; 驱动方式&#xff08;由于蜂鸣器所…

基于SpringBoot+Vue人口老龄化社区服务与管理平台(源码+部署说明+演示视频+源码介绍+lw)

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。&#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通…

企微获客助手到底有哪些价值?

获客助手作为企业微信官方提供的获客工具&#xff0c;在私域布局中确实展现了其强大的引流效率和便利性。这一工具通过简化传统引流过程中的复杂步骤&#xff0c;使得企业能够更高效地吸引和转化潜在客户。此外&#xff0c;获客助手还能实现不同渠道的无缝链接&#xff0c;进一…

【研发日记】Matlab/Simulink开箱报告(十一)——Requirements Toolbox

目录 前言 Requirements Toolbox 编写需求 需求联接设计 需求跟踪开发进度 追溯性矩阵 分析和应用 总结 前言 见《开箱报告&#xff0c;Simulink Toolbox库模块使用指南&#xff08;六&#xff09;——S-Fuction模块&#xff08;TLC&#xff09;》 见《开箱报告&#x…

Kafka开发实录

前言 最近我总是在做大胆的事情&#xff0c;莫不是少年也需要冲冲冲&#xff0c;明明我不是这样的人啊&#xff0c;难不成这就是命运&#xff0c;来自命运石之门的选择&#xff01;废话不多说&#xff0c;本次是Kafka的实战篇&#xff0c;为什么这篇这么快呢&#xff1f;看了开…

网络——协议定制

目录 再谈协议 结构化数据 计算器 封装socket 封装TcpServer服务器 协议定制 问题 解决方法 守护进程 setsid 守护进程函数 json库 序列化和反序列化 再谈协议 原来不是说过什么是协议吗&#xff0c;协议不就是通信双方必须遵守的约定吗&#xff0c;那这就完了吗&…