当前位置: 首页 > news >正文

《算法题讲解指南:动态规划算法--子数组系列》--21.乘积最大子数组,22.乘积为正数的最长子数组

🔥小叶-duck:个人主页

❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

21.乘积最大子数组

题目链接:

题目描述:

题目示例:

解法(动态规划):

算法思路:

C++算法代码:

算法总结及流程解析:

22.乘积为正数的最长子数组

题目链接:

题目描述:

题目示例:

解法(动态规划):

算法思路:

C++算法代码:

算法总结及流程解析:

结束语


21.乘积最大子数组

题目链接:

152. 乘积最大子数组 - 力扣(LeetCode)

题目描述:

题目示例:

解法(动态规划):

算法思路:

这道题与「最大子数组和」非常相似,我们可以效仿着定义一下状态表示以及状态转移:
i.dp[i]表示以i为结尾的所有子数组的最大乘积,
ii. dp[i] = max(nums[i], dp[i - 1] * nums[i]);
由于正负号的存在,我们很容易就可以得到,这样求dp[i]的值是不正确的。因为dp[i-1]的信息并不能让我们得到dp[i]的正确值。比如数组[-2,5,-2],用上述状态转移得到的dp数组为[-2,5,-2],最大乘积为5 。但是实际上的最大乘积应该是所有数相乘,结果为20。
究其原因,就是因为我们在求dp[2]的时候,因为nums[2]是一个负数,因此我们需要的是「i -1 位置结尾的最小的乘积(-10)」,这样一个负数乘以「最小值」,才会得到真实的最大值。
因此,我们不仅需要一个「乘积最大值的dp表」,还需要一个「乘积最小值的dp表」。

1.状态表示:
f[i]表示:以i结尾的所有子数组的最大乘积,
g[i]表示:以i结尾的所有子数组的最小乘积。

2.状态转移方程:
遍历每一个位置的时候,我们要同步更新两个dp 数组的值。
对于f[i],也就是「以为结尾的所有子数组的最大乘积」,对于所有子数组,可以分为下面三种形式:
i.子数组的长度为1,也就是nums[i];
ii.子数组的长度大于1 ,但nums[i]>0,此时需要的是i-1 为结尾的所有子数组的最大乘积f[i-1],再乘上nums[i],也就是nums[i]* f[i- 1];
iii.子数组的长度大于1,但nums[i]<0,此时需要的是i-1为结尾的所有子数组的最小乘积g[i-1],再乘上nums[i],也就是nums[i]* g[i-1];
(如果nums[i]=0,所有子数组的乘积均为0,三种情况其实都包含了)
综上所述,f[i] = max(nums[i], max(nums[i]* f[i - 1], nums[i] * g[i - 1]) ).
对于g[i],也就是「以i为结尾的所有子数组的最小乘积」,对于所有子数组,可以分为下面三种形式:
i.子数组的长度为1,也就是nums[i];
ii.子数组的长度大于1,但nums[i]>0,此时需要的是i-1为结尾的所有子数组的最小乘积g[i-1],再乘上nums[i],也就是nums[i]* g[i-1];
iii.子数组的长度大于1 ,但nums[i]<0 ,此时需要的是i1 为结尾的所有子数组的最大乘积f[i-1],再乘上 nums[i],也就是 nums[i] * f[i-1];
综上所述, g[i] = min(nums[i], min(nums[i] * f[i -1], nums[i] * g[i - 1]))
(如果nums[i]=0,所有子数组的乘积均为0,三种情况其实都包含了)

3.初始化:
可以在最前面加上一个辅助结点,帮助我们初始化。使用这种技巧要注意两个点:
i.辅助结点里面的值要保证后续填表是正确的;
ii.下标的映射关系。
在本题中,最前面加上一个格子,并且让f[0]=g[0]=1即可。

4.填表顺序:
根据状态转移方程易得,填表顺序为「从左往右,两个表一起填」。

5.返回值:
返回 f 表中的最大值。

C++算法代码:

class Solution { public: int maxProduct(vector<int>& nums) { int n = nums.size(); vector<int> max_dp(n); vector<int> min_dp(n); max_dp[0] = min_dp[0] = nums[0]; for(int i = 1; i < n; i++) { if(nums[i] >= 0) { max_dp[i] = max(max_dp[i - 1] * nums[i], nums[i]); min_dp[i] = min(min_dp[i - 1] * nums[i], nums[i]); } else { max_dp[i] = max(min_dp[i - 1] * nums[i], nums[i]); min_dp[i] = min(max_dp[i - 1] * nums[i], nums[i]); } } int ret = INT_MIN; for(int i = 0; i < n; i++) { ret = max(ret, max_dp[i]); } return ret; } };

算法总结及流程解析:

22.乘积为正数的最长子数组

题目链接:

1567. 乘积为正数的最长子数组长度 - 力扣(LeetCode)

题目描述:

题目示例:

解法(动态规划):

算法思路:

继续效仿「最大子数组和」中的状态表示,尝试解决这个问题。
状态表示:dp[i]表示「所有以i 结尾的子数组,乘积为正数的最长子数组的长度」。
思考状态转移:对于i位置上的nums[i],我们可以分三种情况讨论:
i.如果nums[i]=0,那么所有以i为结尾的子数组的乘积都不可能是正数,此时dp[i]=0;
ii.如果nums[i]>0 ,那么直接找到dp[i-1]的值(这里请再读一遍dp[i-1]代表的意义,并且考虑如果dp[i-1]的结值是0 的话,影不影响结果),然后加1即可,此时dp[i]=dp[i-1]+1;
iii.如果nums[i]<0,这时候你该蛋疼了,因为在现有的条件下,你根本没办法得到此时的最长长度。因为乘法是存在「负负得正」的,单单靠一个dp[i-1],我们无法推导出dp[i]的值。
但是,如果我们知道「以i-1为结尾的所有子数组,乘积为负数的最长子数组的长度」neg[i-1],那么此时的dp[i]是不是就等于neg[i-1]+1呢?
通过上面的分析,我们可以得出,需要两个dp表,才能推导出最终的结果。不仅需要一个「乘积为正数的最长子数组」,还需要一个「乘积为负数的最长子数组」。

1.状态表示:
f[i]表示:以i结尾的所有子数组中,乘积为「正数」的最长子数组的长度;
g[i]表示:以i结尾的所有子数组中,乘积为「负数」的最长子数组的长度。

2.状态转移方程:
遍历每一个位置的时候,我们要同步更新两个dp数组的值。
对于f[i],也就是以i为结尾的乘积为「正数」的最长子数组,根据nums[i]的值,可以分为三种情况:
i.nums[i]=0时,所有以i为结尾的子数组的乘积都不可能是正数,此时f[i]=0;
ii. nums[i] > 0时,那么直接找到f[i - 1]的值(这里请再读一遍f[i-1]代表的意义,并且考虑如果f[i-1]的结值是0的话,影不影响结果),然后加一即可,此时 f[i]= f[i- 1] + 1;
iii. nums[i]<0时,此时我们要看g[i-1] 的值(这里请再读一遍 g[i-1]代表的意义。因为负负得正,如果我们知道以i-1为结尾的乘积为负数的最长子数组的长度,加上1即可),根据g[i-1]的值,又要分两种情况:
1. g[i-1]=0 ,说明以i-1 为结尾的乘积为负数的最长子数组是不存在的,又因为nums[i]<0,所以以i 结尾的乘积为正数的最长子数组也是不存在的,此时f[i]=0;
2. g[i - 1] !=0,说明以i-1为结尾的乘积为负数的最长子数组是存在的,又因为nums[i]< 0,所以以i 结尾的乘积为正数的最长子数组就等于g[i-1]+1;
综上所述,nums[i]< 0时,f[i] =g[i-1 == 0 ? 0 : g[i-1] + 1;

对于g[i],也就是以i为结尾的乘积为「负数」的最长子数组,根据nums[i]的值,可以分为三种情况:
i.nums[i]=时,所有以i为结尾的子数组的乘积都不可能是负数,此时g[i]=0;
ii.nums[i] < 0时,那么直接找到f[i-1]的值(这里请再读一遍f[i-1]代表的意义,并且考虑如果f[i-1]的结值是的话,影不影响结果),然后加一即可(因为正数*负数=负数),此时 g[i] = f[i -1]+ 1 ;
iii.nums[i]>0 时,此时我们要看g[i-1]的值(这里请再读一遍 g[i -1]代表的意义。因为正数*负数=负数),根据g[i-1]的值,又要分两种情况:
1.g[i- 1]= ,说明以i- 1 为结尾的乘积为负数的最长子数组是不存在的,又因为nums[i]>0,所以以i结尾的乘积为负数的最长子数组也是不存在的,此时f[i]=0;
2. g[i-1] != 0 ,说明以i-1 为结尾的乘积为负数的最长子数组是存在的,又
因为nums[i]>0,所以以i 结尾的乘积为正数的最长子数组就等于g[i-1] + 1;
综上所述, nums[i] > 0 时, g[i] = g[i -1 == 0 ? 0 : g[i - 1] + 1 ;

这里的推导比较绕,因为不断的出现「正数和负数」的分情况讨论,我们只需根据下面的规则,严格找到此状态下需要的dp 数组即可:
i.正数* 正数=正数
ii.负数*负数=正数
iii.负数*正数=正数*负数=负数

3.初始化:
可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
i.辅助结点里面的值要「保证后续填表是正确的」;
ii. 「下标的映射关系」。
在本题中,最前面加上一个格子,并且让f[0]= g[0]= 0即可。

4.填表顺序:
根据「状态转移方程」易得,填表顺序为「从左往右,两个表一起填」。

5.返回值:
根据「状态表示」,我们要返回f 表中的最大值。

C++算法代码:

class Solution { public: int getMaxLen(vector<int>& nums) { int n = nums.size(); vector<int> pos_dp(n); vector<int> neg_dp(n); if(nums[0] > 0) { pos_dp[0] = 1; neg_dp[0] = 0; } else if(nums[0] < 0) { pos_dp[0] = 0; neg_dp[0] = 1; } else { pos_dp[0] = neg_dp[0] = 0; } for(int i = 1; i < n; i++) { if(nums[i] > 0) { pos_dp[i] = pos_dp[i - 1] + 1; neg_dp[i] = neg_dp[i - 1] == 0 ? 0 : neg_dp[i - 1] + 1; } else if(nums[i] < 0) { pos_dp[i] = neg_dp[i - 1] == 0 ? 0 : neg_dp[i - 1] + 1; neg_dp[i] = pos_dp[i - 1] + 1; } else { pos_dp[i] = neg_dp[i] = 0; } } int ret = INT_MIN; for(int i = 0; i < n; i++) { ret = max(ret, pos_dp[i]); } return ret; } };

算法总结及流程解析:

结束语

到此,21.乘积最大子数组,22.乘积为正数的最长子数组 这两道算法题就讲解完了。对于乘积最大子数组问题:采用双DP数组分别记录以i结尾的最大和最小乘积,通过比较当前元素、与前驱最大乘积或最小乘积的组合来递推求解;对于乘积为正数的最长子数组问题:同样使用双DP数组分别记录以i结尾的正数和负数乘积最长子数组长度,根据当前元素正负分情况讨论状态转移。希望大家能有所收获!

http://www.cnnetsun.cn/news/1678463.html

相关文章:

  • CH32X035 USB MIDI免驱库:RISC-V嵌入式音乐硬件开发指南
  • 嵌入式Linux驱动开发全攻略
  • SX5110轻量级驱动库:Nokia 5110 LCD嵌入式裸金属控制方案
  • Mokosh嵌入式框架:面向ESP32/ESP8266的轻量级物联网开发方案
  • C语言内存错误解析与调试实战指南
  • 精益数据分析系统功能拆解:如何用精益数据分析解决指标虚高难题与初创期验证场景
  • 如何快速上手接口测试?
  • 西门子S7-200SMART PLC与组态王7.0通信在压铸机控制中的应用:附带完整程序与多媒体资料
  • 单相级联H桥(CHB)多电平变换器并网仿真,网侧电压220V PR电压外环 ,PI电流内环,有...
  • CPU、寄存器、内存、指令:2小时极简入门【20260403】---大白话-从买菜到造火箭
  • AUTOSAR通信栈揭秘:Basic CAN和Full CAN的底层实现与性能对比
  • Cartographer配置踩坑实录:从‘odom’报错到流畅建图的完整避坑指南
  • GitHub精选:5款高效开源校园管理系统助力教育数字化转型
  • OpenCV TrackBar(轨迹条)超详细用法教程
  • 2025最权威的五大AI科研神器推荐
  • 如何避免被 Google 惩罚和降权_移动端优化对 SEO 有什么要求
  • 实测nanobot:5分钟搭建个人AI助手,还能轻松接入QQ聊天
  • 【技术干货】从 Kilo 重构 VS Code 扩展,看多智能体并行 AI 编程的新范式
  • 导论:为什么要学C语言
  • Dify 工作流/应用中的上下文变量提示
  • 斑斑AI vs 氚云:2026中小企业低代码平台选型全解析
  • 魔方财务批量拉取产品信息教程
  • 解锁论文写作新境界:书匠策AI——学术探索的智能导航灯
  • 我开发了 3 个 OpenClaw Skill,分享我的设计思路
  • Google Gemma 4 正式发布:Apache 2.0 开源许可 + 256K 上下文 + Agent 原生支持全面解读
  • C盘空间急救:使用TreeSize成功找回50GB空间
  • vxWorks6.8/6.9 操作系统下 QT 安装设置及运行方法
  • 覆盖数十个行业,GEO 如何帮不同赛道企业实现精准获客?
  • 2026年专业深度测评:超强增压花洒套装排名前五权威榜单
  • 团队技术专家的技术设计模版,真心好用!