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

LeetCode 152题别再用暴力了!一个动画看懂动态规划如何搞定乘积最大子数组

动态规划解乘积最大子数组:从思维误区到可视化理解

很多人在解决LeetCode 152题"乘积最大子数组"时,会陷入一个常见的思维陷阱——仅维护最大值状态。这种看似合理的简化思路,在面对负数时会导致计算结果完全错误。让我们通过一个实际例子来揭示这个陷阱:

nums = [2, 3, -2, 4]

如果只记录最大值,当遇到第一个-2时,我们会得到:

  • 前一步最大值:6 (2×3)
  • 当前值:-2
  • 新"最大值":max(-2, 6×-2) = -2

这显然忽略了-12这个乘积实际上可能成为后续计算的关键(如果下一个数是负数,-12×负数会变成更大的正数)。

1. 为什么需要同时维护最大和最小值?

动态规划的核心在于状态定义。对于乘积问题,我们需要同时跟踪两个状态:

  • max_dp[i]:以nums[i]结尾的子数组的最大乘积
  • min_dp[i]:以nums[i]结尾的子数组的最小乘积

这种双状态设计源于乘法的一个独特性质:负负得正。具体来说:

  1. 当nums[i]为正数时:

    • 最大乘积 = max(nums[i], 前一个最大乘积 × nums[i])
    • 最小乘积 = min(nums[i], 前一个最小乘积 × nums[i])
  2. 当nums[i]为负数时:

    • 最大乘积 = max(nums[i], 前一个最小乘积 × nums[i])
    • 最小乘积 = min(nums[i], 前一个最大乘积 × nums[i])

关键提示:最小值状态看似多余,实则是为后续可能出现的负数做准备,确保不会错过"负负得正"的机会。

2. 状态转移的可视化过程

让我们用表格形式展示示例数组[2, 3, -2, 4]的完整计算过程:

数字当前值前max前min新max计算新min计算最终max最终min全局max
22--max(2, N/A)min(2, N/A)222
3322max(3, 2×3)min(3, 2×3)636
-2-263max(-2, 6×-2, 3×-2)min(-2, 6×-2, 3×-2)-2-126
44-2-12max(4, -2×4, -12×4)min(4, -2×4, -12×4)4-486

从表格中可以清晰看到:

  • 处理数字3时,常规思路有效
  • 处理-2时,最小值-12被记录下来
  • 虽然最后一步没有用到-12,但如果数组继续有负数,这个最小值就可能变成最大值

3. 代码实现的关键细节

基于上述分析,我们来看C++和Java的实现要点:

C++实现

int maxProduct(vector<int>& nums) { int n = nums.size(); if (n == 0) return 0; int max_prod = nums[0]; int min_prod = nums[0]; int result = nums[0]; for (int i = 1; i < n; i++) { int curr = nums[i]; // 需要临时变量,因为max_prod会被修改 int temp_max = max(curr, max(max_prod * curr, min_prod * curr)); min_prod = min(curr, min(max_prod * curr, min_prod * curr)); max_prod = temp_max; result = max(result, max_prod); } return result; }

Java实现

public int maxProduct(int[] nums) { if (nums.length == 0) return 0; int maxProd = nums[0]; int minProd = nums[0]; int result = nums[0]; for (int i = 1; i < nums.length; i++) { int curr = nums[i]; int tempMax = Math.max(curr, Math.max(maxProd * curr, minProd * curr)); minProd = Math.min(curr, Math.min(maxProd * curr, minProd * curr)); maxProd = tempMax; result = Math.max(result, maxProd); } return result; }

实现中的几个关键点:

  1. 使用临时变量保存新的max_prod,因为原始值在计算min_prod时还需要使用
  2. 每次迭代都更新全局最大值result
  3. 边界情况处理(空数组)

4. 算法优化与空间复杂度

观察状态转移方程可以发现,当前状态只依赖于前一个状态,因此我们可以将空间复杂度从O(n)优化到O(1):

def maxProduct(nums): if not nums: return 0 max_prod = min_prod = result = nums[0] for num in nums[1:]: # 同时计算,避免使用临时变量 candidates = (num, max_prod * num, min_prod * num) max_prod, min_prod = max(candidates), min(candidates) result = max(result, max_prod) return result

这种优化在面试中尤为重要,它展示了我们对动态规划状态压缩的理解。时间复杂度保持O(n),因为只需要一次遍历。

5. 常见错误与测试用例

为了验证算法的正确性,以下是一些边界测试用例:

  1. 单元素数组:

    • 输入:[5] → 输出:5
    • 输入:[-3] → 输出:-3
  2. 包含零的数组:

    • 输入:[2,0,3] → 输出:3
    • 输入:[-2,0,-1] → 输出:0
  3. 全负数数组:

    • 输入:[-2,-3,-1] → 输出:6
    • 输入:[-1,-2,-3] → 输出:6
  4. 交替正负:

    • 输入:[2,-5,3,1,-2,4,-1] → 输出:120 (3×1×-2×4×-1)

在解决这个问题时,我最初也犯了只维护最大值的错误。直到用[-2,3,-4]这个测试用例时才发现问题——正确结果应该是24,但错误算法只能得到3。这个教训让我深刻理解了同时维护最大最小值的必要性。

http://www.jsqmd.com/news/529763/

相关文章:

  • 造相 Z-Image 应用场景落地:AI绘画教学、提示词工程测试与安全批量预览
  • 2026年 桁架机械手厂家实力推荐榜:重载/上下料/龙门/三轴/码垛/搬运全系列,机械人地轨焊接/码垛/搬运精选,技术领先与高效稳定之选 - 品牌企业推荐师(官方)
  • 实战指南:如何用RoBERTa+TextCNN搭建高精度意图识别模型(附完整代码)
  • 究极智能体·唯道可驭·唯心可掌
  • uWSGI部署深度学习模型报错:共享库映射失败的深度解析与解决方案
  • ComfyUI实战体验:用可视化节点快速生成高质量AI绘画作品
  • 20254118于欣灵实验一《Python程序设计》实验报告
  • 5个革新性功能:WebLaTex的学术写作效率提升方案
  • ControlNet-v1-1_fp16技术指南:跨版本兼容与高效部署全攻略
  • Redis大Key隐患:排查与根治指南
  • 天道序章·究极明证
  • Claude3-Vision vs Qwen3-VL:长文档解析能力对比
  • 电力电子仿真总翻车?试试用PSIM+MATLAB联合仿真,解决Simulink电流波形不准的难题
  • 计算机视觉突破:二维图像深度增强的自动化法线贴图生成技术研究
  • Escape From Tarkov 训练器终极指南:从安装到精通的全方位解决方案
  • 12李军浩
  • 使用LaTeX撰写集成StructBERT模型的学术论文
  • B站无损音频提取实战指南:从入门到精通的全流程解析
  • 用随机森林填补缺失值?一份基于sklearn的完整数据清洗实战与性能对比
  • 开源投屏工具:实现手机电脑无缝协同的完整方案
  • 2026年双面胶厂家推荐排行榜:无痕/PET/棉纸/耐高温/阻燃/高温胶纸,源头工厂精选与专业性能深度解析 - 品牌企业推荐师(官方)
  • GTE中文-large效果惊艳:中文网络流行语(如‘绝绝子’‘泰酷辣’)情感极性漂移追踪
  • 2026年 导轨厂家推荐排行榜:直线导轨/滚柱导轨/滚珠导轨/上银导轨/TBI导轨/国产导轨/高精度导轨/机床导轨,精密传动与稳定耐用之选 - 品牌企业推荐师(官方)
  • 数据结构:动态单链表的实现
  • 别再乱配CorsFilter了!SpringBoot项目打War包丢进Tomcat,跨域配置的正确姿势
  • 手把手教你用HTML5打造个性化音乐播放器(支持网易云/QQ音乐解析)
  • 城市内涝积水监测系统
  • 20254206 实验一 《Python程序设计》实验报告
  • 数据结构:静态链表与list
  • 深入解析SX126x的BUSY引脚:如何避免SPI命令冲突与数据丢失