算法沉淀——动态规划之子数组、子串系列(上)(leetcode真题剖析)

在这里插入图片描述

算法沉淀——动态规划之子数组、子串系列

  • 01.最大子数组和
  • 02.环形子数组的最大和
  • 03.乘积最大子数组
  • 04.乘积为正数的最长子数组长度

01.最大子数组和

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

给你一个整数数组 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

**进阶:**如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

思路

  1. 状态表示:
    • 使用「经验 + 题目要求」定义线性动态规划的状态表示。
    • 选择以「某个位置为结尾」的方式,结合「题目要求」,定义状态表示:dp[i] 表示以 i 位置元素为结尾的「所有子数组」中和的最大和。
  2. 状态转移方程:
    • 将 dp[i] 的所有可能分为两种情况:子数组的长度为 1 或子数组的长度大于 1。
    • 如果子数组长度为 1,则 dp[i] = nums[i]。
    • 如果子数组长度大于 1,则 dp[i] 应该等于以 i-1 为结尾的「所有子数组」中和的最大值再加上 nums[i],即 dp[i-1] + nums[i]。
    • 转移方程为 dp[i] = max(nums[i], dp[i-1] + nums[i])。
  3. 初始化:
    • 在最前面加上一个「辅助结点」,用于初始化。辅助结点的值要保证后续填表是正确的,因此设 dp[0] = 0。
    • 辅助结点的存在需要注意下标的映射关系。
  4. 填表顺序:
    • 根据「状态转移方程」,填表顺序为「从左往右」。
  5. 返回值:
    • 状态表 dp 表示以 i 为结尾的所有子数组的最大值,但最大子数组和的结尾是不确定的。因此,返回整个 dp 表中的最大值。

代码

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;}
};

02.环形子数组的最大和

题目链接:https://leetcode.cn/problems/maximum-sum-circular-subarray/

给定一个长度为 n环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和

环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n]nums[i] 的前一个元素是 nums[(i - 1 + n) % n]

子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n

示例 1:

输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3

示例 2:

输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10

示例 3:

输入:nums = [3,-2,2,-3]
输出:3
解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3

提示:

  • n == nums.length
  • 1 <= n <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104

思路

这个问题与「最大子数组和」的区别在于需要考虑数组首尾相连的情况。结果可能有两种情况:一是在数组的内部,包括整个数组;二是在数组首尾相连的一部分上。

  1. 对于第一种情况,按照「最大子数组和」的方法得到结果,记为 fmax。
  2. 对于第二种情况,分析得出,第二种情况的最大和应等于数组总和减去最小子数组和。其中,最小子数组和表示为 gmin。
  3. 两种情况下的最大值即为所求结果。然而,需要特殊处理数组内全部为负数的情况,因为直接比较两者的最大值可能会得到 0。这种情况下,实际结果应为数组内的最大值。
  4. 对于「最小子数组和」的求解过程与「最大子数组和」相同,使用 f 表示最大和,g 表示最小和。
  5. 返回值:先找到 f 表的最大值 fmax;找到 g 表的最小值 gmin;统计所有元素的和 sum;返回 sum == gmin ? fmax : max(fmax, sum - gmin)。

代码

class Solution {
public:int maxSubarraySumCircular(vector<int>& nums) {int n=nums.size();vector<int> f(n+1);vector<int> g(n+1);int fmax=INT_MIN,gmin=INT_MAX,sum=0;for(int i=1;i<=n;++i){int x=nums[i-1];sum+=x;f[i]=max(x,x+f[i-1]);fmax=max(fmax,f[i]);g[i]=min(x,x+g[i-1]);gmin=min(gmin,g[i]);}return sum==gmin ? fmax : max(fmax,sum-gmin);}
};

03.乘积最大子数组

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

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

子数组 是数组的连续子序列。

示例 1:

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

示例 2:

输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

提示:

  • 1 <= nums.length <= 2 * 104
  • -10 <= nums[i] <= 10
  • nums 的任何前缀或后缀的乘积都 保证 是一个 32-位 整数

思路

这道题类似于「最大子数组和」,但需要考虑乘积而非和。定义两个状态数组 f[i] 和 g[i] 分别表示以 i 为结尾的所有子数组的最大乘积和最小乘积。

  1. 状态表示:
    • f[i] 表示以 i 为结尾的所有子数组的最大乘积。
    • g[i] 表示以 i 为结尾的所有子数组的最小乘积。
  2. 状态转移方程:
    • 对于 f[i],需要考虑三种情况:子数组长度为 1,子数组长度大于 1 且 nums[i] > 0,子数组长度大于 1 且 nums[i] < 0。
    • 综上,f[i] = max(nums[i], max(nums[i] * f[i - 1], nums[i] * g[i - 1]))
    • 对于 g[i],同样考虑三种情况。
    • 综上,g[i] = min(nums[i], min(nums[i] * f[i - 1], nums[i] * g[i - 1]))
  3. 初始化:
    • 在最前面加上一个辅助结点,并设置 f[0] = g[0] = 1
  4. 填表顺序:
    • 从左往右,两个表一起填。
  5. 返回值:
    • 返回 f 表中的最大值。

代码

class Solution {
public:int maxProduct(vector<int>& nums) {int n=nums.size();vector<int> f(n+1);vector<int> g(n+1);f[0]=g[0]=1;int ret=INT_MIN;for(int i=1;i<=n;++i){int x=nums[i-1],y=f[i-1]*nums[i-1],z=g[i-1]*nums[i-1];f[i]=max(x,max(y,z));g[i]=min(x,min(y,z));ret=max(ret,f[i]);}return ret;}
};

04.乘积为正数的最长子数组长度

题目链接:https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/

定一个整数数组nums,找到其中所有元素的乘积为正的子数组的最大长度。

数组的子数组是从该数组中取出的零个或多个值的连续序列。

返回具有正积的子数组的最大长度

示例1:

输入: nums = [1,-2,-3,4]
输出: 4
解释:数组 nums 的乘积已经为 24。

示例2:

输入: nums = [0,1,-2,-3,-4]
输出: 3
解释:具有正积的最长子数组是 [1,-2,-3],其积为 6。
请注意,我们不能在子数组中包含 0,因为这会使乘积为 0,而 0 不是正数。

示例3:

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

限制条件:

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

思路

1. 状态表达: 定义两个动态规划数组 fg,其中:

  • f[i] 表示以 i 为结尾的所有子数组中,乘积为正数的最长子数组长度。
  • g[i] 表示以 i 为结尾的所有子数组中,乘积为负数的最长子数组长度。

2. 状态转移方程: 对于 f[i],根据当前元素 nums[i] 的值,分三种情况讨论:

  • 如果 nums[i] = 0,说明当前子数组的乘积为零,所以 f[i] = 0

  • 如果 nums[i] > 0,说明当前子数组的乘积为正数,直接找到 f[i - 1] 的值并加一,即 f[i] = f[i - 1] + 1

  • 如果

    nums[i] < 0
    

    ,需要根据

    g[i - 1]
    

    的值来判断:

    • 如果 g[i - 1] 为零,表示以 i - 1 为结尾的最长负数子数组不存在,所以 f[i] = 0
    • 如果 g[i - 1] 不为零,表示以 i - 1 为结尾的最长负数子数组存在,此时 f[i] = g[i - 1] + 1

对于 g[i],也分三种情况讨论:

  • 如果 nums[i] = 0,说明当前子数组的乘积为零,所以 g[i] = 0

  • 如果 nums[i] < 0,说明当前子数组的乘积为负数,直接找到 f[i - 1] 的值并加一,即 g[i] = f[i - 1] + 1

  • 如果

    nums[i] > 0
    

    ,需要根据

    g[i - 1]
    

    的值来判断:

    • 如果 g[i - 1] 为零,表示以 i - 1 为结尾的最长负数子数组不存在,所以 g[i] = 0
    • 如果 g[i - 1] 不为零,表示以 i - 1 为结尾的最长负数子数组存在,此时 g[i] = g[i - 1] + 1

3. 初始化: 在数组最前面加上一个辅助结点,设置 f[0] = g[0] = 0

4. 填表顺序: 从左往右遍历数组,同时填充 fg 两个动态规划数组。

5. 返回值: 返回 f 数组中的最大值,即为最终结果。

代码

class Solution {
public:int getMaxLen(vector<int>& nums) {int n=nums.size();vector<int> f(n+1);vector<int> g(n+1);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;}
};

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GDB动态调试学习-2-【断点 观察点】

文章目录 在程序地址上打断点在程序入口处打断点获取程序入口地址 在命名空间设置断点命名空间给命名空间的函数下断电 在文件行号上打断点保存已经设置的断点设置临时断点设置条件断点忽略断点 在程序地址上打断点 当调试汇编程序&#xff0c;或者没有调试信息的程序时&#…

【c语言】if 选择语句

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&…

项目登录方案选型

一.Cookie + Session 登录 大家都知道,HTTP 是一种无状态的协议。无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求返回数据,但不会记录任何信息。为了解决 HTTP 无状态的问题,出现了 Cookie。Co…

网络编程(JAVA)

前言&#xff1a;Java 是 Internet 上的语言&#xff0c;它从语言级上提供了对网络应用程序的支持&#xff0c;程序员能够很容易开发常见的网络应用程序。 Java 提供的网络类库&#xff0c;可以实现无痛的网络连接&#xff0c;联网的底层细节被隐藏在 Java 的本机安装系统里&a…

【一】【SQL】表的增删查改(部分)

表之“增”操作 建表的操作 mysql> create table students(-> id int unsigned primary key auto_increment,-> sn int unsigned unique key,-> name varchar(20) not null,-> qq varchar(32) unique key-> ); Query OK, 0 rows affected (0.03 sec)mysql&g…

代码随想录Leetcode474. 一和零

题目&#xff1a; 代码(首刷看解析 2024年2月26日&#xff09; class Solution { public:// 二维 0 1背包int findMaxForm(vector<string>& strs, int m, int n) {// 1 二维 [i]表示 0 的个数&#xff0c;上限m; [j]表示 1 的个数&#xff0c;上限nvector<vector…

CMU15445实验总结(Spring 2023)

CMU15445实验总结(Spring 2023) 背景 菜鸟博主是2024届毕业生&#xff0c;学历背景太差&#xff0c;导致23年秋招无果&#xff0c;准备奋战春招。此前有读过LevelDB源码的经历&#xff0c;对数据库的了解也仅限于LevelDB。奔着”有对比才能学的深“的理念&#xff0c;以及缓解…

【深度学习:微模型过度拟合】微模型简介或:我如何学会停止担忧并热爱过度拟合

【深度学习&#xff1a;微模型过度拟合】微模型简介或&#xff1a;我如何学会停止担忧并热爱过度拟合 起源微模型到底是什么&#xff1f;一维标记蝙蝠侠效率额外的好处面向数据的编程 这篇文章的目的是介绍我们在 Encord 中用于自动化数据注释的“微模型”方法。我们已将这种方…

测试环境搭建整套大数据系统(七:集群搭建kafka(2.13)+flink(1.14)+dinky+hudi)

一&#xff1a;搭建kafka。 1. 三台机器执行以下命令。 cd /opt wget wget https://dlcdn.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz tar zxvf kafka_2.13-3.6.1.tgz cd kafka_2.13-3.6.1/config vim server.properties修改以下俩内容 1.三台机器分别给予各自的broker_id…

【nvm】下载安装及使用(包含windows和Linux)

目录 1、Windows版本下载及安装 2、Linux下载及安装 下载 安装 3、使用 4、附加 在不借助第三方工具的情况下切换node版本&#xff0c;只能卸载现有版本&#xff0c;安装需要的版本&#xff0c;这样显然很麻烦。而nvm就很好的帮我们解决了这个问题。 nvm&#xff08;nod…

GEE数据集——全球 30 米不透水表面动态数据集 (GISD30)

全球 30 米不透水表面动态数据集 (GISD30) 全球 30 米不透水表面动态数据集 (GISD30) 为了解 1985 年至 2020 年全球不断变化的不透水表面景观提供了宝贵的资源。该数据集在城市可持续发展、人为碳排放评估和全球生态环境建模等领域具有深远的科学意义和实际应用价值。GISD30 是…

全域增长方法论:帮助品牌实现科学经营,助力长效生意增长

前两年由于疫情反复、供给需求收缩等条件制约&#xff0c;品牌业务均受到不同程度的影响。以双十一和618电商大促为例&#xff0c;就相比往年颇显“惨淡”&#xff0c;大多品牌营销都无法达到理想预期。 随着市场环境不断开放&#xff0c;2023年营销行业开始从低迷期走上了高速…