Java版LeetCode热题100之乘积最大子数组:动态规划中的正负博弈与空间优化艺术

Java版LeetCode热题100之乘积最大子数组:动态规划中的正负博弈与空间优化艺术

本文深入剖析 LeetCode 第152题「乘积最大子数组」,从问题本质出发,详解为何普通最大子序和思路失效,如何通过维护最大/最小值双状态解决负数翻转问题,并涵盖代码实现、复杂度分析、面试高频问题、实际应用场景及延伸思考,是一篇面向中高级开发者的高质量技术博客。


一、原题回顾

题目编号:LeetCode 152
题目名称:乘积最大子数组(Maximum Product Subarray)
难度等级:中等(Medium)

题目描述

给你一个整数数组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 * 10⁴
  • -10 <= nums[i] <= 10
  • nums的任何子数组的乘积都保证是一个 32 位整数

二、原题分析

这道题看似是「最大子序和」(LeetCode 53)的乘法版本,但本质完全不同

2.1 为什么不能直接套用最大子序和的思路?

在最大子序和中,我们定义dp[i] = max(dp[i-1] + nums[i], nums[i]),因为加法具有单调性:更大的前缀和总能带来更大的当前和。

乘法不具备这种单调性!原因在于:

  • 负数的存在:两个负数相乘得正数;
  • 零的存在:任何数乘以 0 得 0,会“截断”序列;
  • 正负翻转:当前最大值可能由之前的最小值(负数)乘以当前负数得到。

2.2 关键洞察:负负得正

考虑数组[5, 6, -3, 4, -3]

  • 前两数乘积:30(最大)
  • 全体乘积:5×6×(-3)×4×(-3) =1080(更大!)

问题出在哪里?
当我们遇到第二个-3时,之前累积的最小值(即最负的值)5×6×(-3)×4 = -360,乘以-3后变为1080,远超之前的30

因此,仅维护“以 i 结尾的最大乘积”是不够的,还必须维护“以 i 结尾的最小乘积”


三、答案构思:动态规划(双状态)

3.1 状态定义

我们定义两个状态:

  • maxF[i]:以nums[i]结尾的最大子数组乘积;
  • minF[i]:以nums[i]结尾的最小子数组乘积。

注意:nums[i]必须被包含。

3.2 状态转移方程

对于每个位置i,我们有三种选择:

  1. 只取nums[i]
  2. nums[i]接到以i-1结尾的最大乘积后:maxF[i-1] * nums[i]
  3. nums[i]接到以i-1结尾的最小乘积后:minF[i-1] * nums[i]

由于nums[i]可能为正、负或零,我们需要同时考虑最大和最小:

maxF [ i ] = max ⁡ ( maxF [ i − 1 ] × nums [ i ] , minF [ i − 1 ] × nums [ i ] , nums [ i ] ) minF [ i ] = min ⁡ ( maxF [ i − 1 ] × nums [ i ] , minF [ i − 1 ] × nums [ i ] , nums [ i ] ) \begin{aligned} \text{maxF}[i] &= \max(\text{maxF}[i-1] \times \text{nums}[i],\ \text{minF}[i-1] \times \text{nums}[i],\ \text{nums}[i]) \\ \text{minF}[i] &= \min(\text{maxF}[i-1] \times \text{nums}[i],\ \text{minF}[i-1] \times \text{nums}[i],\ \text{nums}[i]) \end{aligned}maxF[i]minF[i]=max(maxF[i1]×nums[i],minF[i1]×nums[i],nums[i])=min(maxF[i1]×nums[i],minF[i1]×nums[i],nums[i])

3.3 初始条件与结果

  • 初始:maxF[0] = minF[0] = nums[0]
  • 最终答案:max(maxF[0], maxF[1], ..., maxF[n-1])

四、完整答案(方法一:数组版 DP)

publicclassSolution{publicintmaxProduct(int[]nums){if(nums==null||nums.length==0){return0;}intn=nums.length;// 使用 long 防止中间计算溢出(虽然题目保证结果在32位内,但中间可能溢出)long[]maxF=newlong[n];long[]minF=newlong[n];// 初始化maxF[0]=minF[0]=nums[0];intresult=nums[0];for(inti=1;i<n;i++){longcurrent=nums[i];// 计算三种可能的值longcandidate1=maxF[i-1]*current;longcandidate2=minF[i-1]*current;maxF[i]=Math.max(current,Math.max(candidate1,candidate2));minF[i]=Math.min(current,Math.min(candidate1,candidate2));result=Math.max(result,(int)maxF[i]);}returnresult;}}

代码说明

  • 使用long类型避免中间乘积溢出(如10^5 * 10^5 = 10^10 > 2^31);
  • 显式计算三个候选值,逻辑清晰;
  • 实时更新全局最大值result

五、代码分析(方法一)

5.1 正确性验证

nums = [2,3,-2,4]为例:

inums[i]maxF[i-1]minF[i-1]候选值maxF[i]minF[i]
02---22
13226,6,363
2-263-12,-6,-2-2-12
34-2-12-8,-48,44-48

全局最大值:max(2,6,-2,4) = 6

再看nums = [-2,0,-1]

inums[i]maxFminF
0-2-2-2
10max(0, 0, 0)=0min(0,0,0)=0
2-1max(-1, 0, 0)=-1? → 实际:max(-1, 0*(-1)=0, 0*(-1)=0) = 0min(-1,0,0)=-1

全局最大值:max(-2,0,-1) = 0

5.2 边界处理

  • 单元素数组:直接返回该元素;
  • 全零数组:返回 0;
  • 全负数组:返回最大(即绝对值最小)的负数;
  • 包含零:零会重置状态,相当于分割数组。

六、时间复杂度与空间复杂度分析(方法一)

时间复杂度:O(n)

  • 单次遍历数组;
  • 每次进行常数次比较和乘法;
  • 总计:O(n)

空间复杂度:O(n)

  • 两个长度为 n 的long数组;
  • 总计:O(n)

对于n=20000,占用约 320KB 内存(2 × 20000 × 8 bytes),可接受,但可进一步优化。


七、答案构思:方法二 —— 空间优化(滚动变量)

7.1 优化思想

观察状态转移方程:

  • maxF[i]minF[i]仅依赖于maxF[i-1]minF[i-1]
  • 因此无需存储整个数组,只需两个变量保存前一状态。

这就是经典的滚动数组(Rolling Array)优化。

7.2 注意事项

在更新maxFminF时,必须先保存旧值,否则maxF更新后会影响minF的计算。

例如:

// 错误写法maxF=Math.max(...,minF*nums[i]);// 此时 minF 还是旧的minF=Math.min(...,maxF*nums[i]);// 但 maxF 已被更新!

正确做法:先缓存prevMax = maxF,prevMin = minF


八、完整答案(方法二:空间优化版)

publicclassSolution{publicintmaxProduct(int[]nums){if(nums==null||nums.length==0){return0;}// 使用 long 防止中间溢出longmaxF=nums[0];// 当前最大乘积longminF=nums[0];// 当前最小乘积intresult=nums[0];for(inti=1;i<nums.length;i++){longcurrent=nums[i];// 保存上一轮的状态longprevMax=maxF;longprevMin=minF;// 更新当前状态maxF=Math.max(current,Math.max(prevMax*current,prevMin*current));minF=Math.min(current,Math.min(prevMax*current,prevMin*current));// 更新全局最大值result=Math.max(result,(int)maxF);}returnresult;}}

代码优势

  • 空间复杂度降至 O(1)
  • 逻辑清晰,无冗余存储;
  • 性能更优,尤其在大数据量时。

九、代码分析(方法二)

9.1 为何使用long

虽然题目保证最终结果在 32 位整数范围内,但中间计算可能溢出

例如:nums = [10000, 10000],中间乘积为100000000,仍在 int 范围内;
但若nums = [50000, 50000](虽然本题约束|nums[i]| ≤ 10,不会发生),则会溢出。

使用long是一种防御性编程,确保算法鲁棒性。

注:本题|nums[i]| ≤ 10n ≤ 20000,最大乘积为10^20000,远超 long 范围!
但题目明确说明“任何子数组的乘积都保证是 32 位整数”,故中间值虽可能很大,但不会导致错误比较(因最终结果在 int 内,且我们只关心相对大小)。

实际上,由于存在负数和零,乘积不会无限增长。但在通用场景下,使用long是良好实践。

9.2 关于溢出处理的讨论

原题提供的参考代码中有:

if(minF<(-1<<31)){minF=nums[i];}

这是试图防止longint时溢出。但这是不必要的,因为:

  • 我们只在最后将maxF转为int用于比较;
  • 题目保证最终结果在 32 位整数内;
  • 中间long值即使很大,只要比较逻辑正确,不影响结果。

因此,无需特殊溢出处理,上述判断可删除。


十、常见问题解答(FAQ)

Q1:为什么需要同时维护最大值和最小值?

A:因为负数会翻转大小关系。当前最大值可能由之前的最小值(负数)乘以当前负数得到。例如:minF = -10,nums[i] = -2product = 20,可能成为新的最大值。

Q2:如果数组中全是正数,是否可以简化?

A:可以!此时minF[i] = nums[i](因为累积乘积只会越来越大),maxF[i] = maxF[i-1] * nums[i]。但为了代码统一性,通常不单独处理。

Q3:零如何影响状态?

A:当nums[i] = 0时:

  • maxF[i] = max(0, 0, 0) = 0
  • minF[i] = min(0, 0, 0) = 0
  • 相当于“重置”状态,后续计算从 0 开始。

Q4:能否只用一个变量?

A:不能。因为最大值和最小值相互依赖,且无法从单一状态推导出另一个。


十一、优化思路拓展

11.1 分治法(理论可行,实际不优)

将数组分为左右两半,最大乘积子数组可能:

  • 完全在左半;
  • 完全在右半;
  • 跨越中点。

跨越中点的情况需计算:

  • 从中点向左的最大/最小乘积;
  • 从中点向右的最大/最小乘积;
  • 组合四种情况(左大×右大,左大×右小,左小×右大,左小×右小)。

时间复杂度 O(n log n),不如 DP 的 O(n),故不推荐。

11.2 处理超大数(BigInteger)

若题目不限制结果范围,需使用BigInteger,但性能较差,且本题无需。

11.3 并行化?

由于状态依赖前一状态,难以并行。但若只需求近似解,可分段处理后合并(非精确)。


十二、数据结构与算法基础知识点回顾

12.1 动态规划的适用条件

  • 最优子结构:全局最优解包含子问题最优解;
  • 重叠子问题:子问题被重复计算;
  • 无后效性:当前状态只与过去有关。

本题中,以i结尾的最大乘积仅依赖i-1的状态,满足无后效性。

12.2 滚动数组优化

  • 适用于状态仅依赖前 k 个状态的问题;
  • 将空间复杂度从 O(n) 降至 O(k);
  • 常见于斐波那契、背包、本题等。

12.3 负数的特殊性

在涉及乘法的优化问题中,负数会:

  • 改变单调性;
  • 导致最大/最小值互换;
  • 需要同时跟踪极值。

这是本题区别于“最大子序和”的核心。

12.4 整数溢出处理

  • Java 中int范围:[-2³¹, 2³¹-1]
  • 乘法易溢出,使用long是常用技巧;
  • 但需注意:long也可能溢出(本题因约束安全)。

十三、面试官提问环节(模拟对话)

面试官:你为什么认为最大子序和的思路在这里行不通?

:因为加法具有单调性——更大的前缀和总能带来更大的当前和。但乘法没有:负数会使大小关系反转。例如,前缀积为 -10(很小),当前数为 -2,乘积为 20(很大)。所以必须同时跟踪最大和最小值。

面试官:你的代码用了 long,但题目说结果是 32 位整数,有必要吗?

:有必要。虽然最终结果在 int 范围内,但中间计算可能溢出。例如,假设我们有 [100000, 100000](尽管本题约束不会出现),乘积为 10¹⁰,超过 int 范围。使用 long 确保中间比较正确。这是一种防御性编程。

面试官:如果数组中有很多零,你的算法效率如何?

:依然 O(n)。零会使 maxF 和 minF 重置为 0,但后续计算正常进行。算法天然处理了零的情况,无需特殊分支。

面试官:能否修改算法以返回具体的子数组,而不仅是乘积?

:可以,但需要额外记录起始位置。每当 maxF 更新时,记录当前结束位置和对应的起始位置(可通过维护 start/end 索引实现)。但会增加空间复杂度至 O(n)。


十四、这道算法题在实际开发中的应用

14.1 金融风控

在股票收益率序列中,寻找连续时间段内最大复合收益率(收益率相乘)。负收益率代表亏损,需考虑“亏损后反弹”的情况。

14.2 信号处理

在音频或图像处理中,某些特征值可能为负,需检测能量(平方或绝对值)最大的连续片段,但若保留符号,则需类似 LIS 的乘积分析。

14.3 游戏开发

在 RPG 游戏中,角色有增益/减益 buff(如攻击力 ×1.5 或 ×0.5),需计算连续战斗中最大伤害输出窗口

14.4 数据压缩

在某些编码方案中,数据块的“权重”为乘积形式,需找到权重最大的连续块以优化存储。

14.5 机器学习

在概率模型中,联合概率为各条件概率的乘积。若对数概率不可用,需直接处理乘积,此时最大概率路径可建模为本题。


十五、相关题目推荐

题号题目关联点
53. 最大子序和加法版本对比学习
1567. 乘积为正数的最长子数组长度符号分析只关心正负
918. 环形子数组的最大和环形扩展更复杂结构
1371. 每个元音包含偶数次的最长子字符串状态压缩异或思想
238. 除自身以外数组的乘积乘积处理前后缀乘积

十六、总结与延伸

16.1 核心收获

  • 负数是乘法优化问题的关键难点
  • 同时维护最大/最小值是处理符号翻转的标准技巧
  • 滚动数组优化是 DP 空间优化的经典手段
  • 理解问题本质(而非套模板)是解决算法题的核心

16.2 延伸思考

  • 如果允许删除一个元素?可分别计算删除每个位置后的最大乘积,时间复杂度 O(n²),或用前后缀乘积优化至 O(n)。
  • 如果数组是环形的?需考虑跨越首尾的情况,类似环形子数组最大和。
  • 如果要求乘积为正的最大长度?只需跟踪符号变化,无需具体数值。

16.3 给读者的建议

  • 不要死记硬背代码,理解“为何需要 minF”;
  • 多画状态转移表,尤其是包含负数和零的例子;
  • 面试中先写清晰的数组版 DP,再优化到滚动变量;
  • 重视边界测试:全正、全负、含零、单元素等。

结语:乘积最大子数组是一道极具启发性的动态规划题。它打破了“最大子序和”的思维定式,揭示了负数在乘法中的独特作用。掌握它,不仅是为了通过一道面试题,更是为了培养对问题本质的洞察力。希望本文能助你在算法之路上走得更远!


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

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

相关文章

2026年高适配工单系统品牌厂商盘点,靠谱推荐清单

2026年,企业数字化协同进入深水区,工单系统作为连接服务需求与执行闭环的核心工具,已从单一客服场景延伸至全业务链路。随着跨部门协作、现场服务等需求激增,市场对系统的合规性、智能化、集成能力要求显著提升,一…

Java版LeetCode热题100之分割等和子集:从NP完全问题到0-1背包的深度解析

Java版LeetCode热题100之分割等和子集&#xff1a;从NP完全问题到0-1背包的深度解析 本文全面剖析 LeetCode 第416题「分割等和子集」&#xff0c;这是一道经典的 NP 完全问题&#xff0c;可转化为 0-1 背包模型。文章涵盖题目理解、动态规划建模、二维与一维DP实现、复杂度分析…

【译】Visual Studio 2026 来了:更快、更智能,深受老用户的喜爱

我们非常激动地宣布,Visual Studio 2026 现已正式发布!这一刻是我们与您携手共创的成果。您的反馈对本次版本的塑造作用超过了以往任何一次。自 2025 年 9 月推出 Insiders 通道以来,下载并测试该预览版的开发者数量…

医学考研党必看!这些资料带你稳稳上岸

医学考研党必看!这些资料带你稳稳上岸医学考研内卷有多激烈不用多说吧?每年都是千军万马过独木桥,选对资料直接少走一半弯路!作为深耕教育领域的博主,今天就把私藏的医学考研资料清单整理出来了,从基础到冲刺全覆…

Java锁优化:从synchronized到CAS的演进与实战选择

文章目录 &#x1f4ca;&#x1f4cb; 一、 序言&#xff1a;线程同步的“速度与激情”&#x1f30d;&#x1f4c8; 二、 深度拆解&#xff1a;synchronized的锁升级之路&#x1f6e1;️&#x1f9e9; 2.1 锁的物理载体&#xff1a;Mark Word&#x1f504;&#x1f9f1; 2.2 演…

第六课 · 6.1从 JDBC 到 MyBatis:SQL 工程化是如何发生的?

如果说 ORM 是“对象如何存在于数据库中的体系”&#xff0c; 那 MyBatis&#xff0c;就是这套体系中最靠近数据库的一条工程路线。这一篇不讲 XML 怎么写&#xff0c;不讲分页插件&#xff0c;不教 CRUD。 我们只回答一个问题&#xff1a;&#x1f449; 为什么 JDBC 一定会进化…

Java版LeetCode热题100之最长有效括号:三种解法深度剖析与算法思维升华

Java版LeetCode热题100之最长有效括号&#xff1a;三种解法深度剖析与算法思维升华 本文全面解析 LeetCode 第32题「最长有效括号」&#xff0c;这是一道考察字符串处理、动态规划、栈应用及双指针技巧的经典难题。文章涵盖题目理解、三种主流解法&#xff08;DP、栈、双指针&a…

2026申请香港优才中介机构有哪些:从政策适配到服务全面对比

2026想办理香港优才申请中介推荐哪家?寰行盛世成为众多精英的首选香港身份服务机构。 2026年,香港优才计划(优秀人才入境计划)凭借“无投资、无雇主要求、无名额限制”的核心优势,仍是内地人才赴港的首选路径。但…

客船按需定制厂家怎么选?青岛雷旺达船舶是优选

在水上旅游蓬勃发展的当下,一艘契合景区特色与运营需求的客船,是提升游客体验、拓展业务边界的核心载体。面对市场上琳琅满目的客船制造商,如何挑选兼具品质、定制化能力与售后保障的合作伙伴?以下结合客船按需定制…

学长亲荐2026TOP10AI论文工具:本科生毕业论文必备测评

学长亲荐2026TOP10AI论文工具&#xff1a;本科生毕业论文必备测评 2026年学术AI写作工具测评&#xff1a;为何值得一看&#xff1f; 随着人工智能技术的不断进步&#xff0c;AI论文工具在学术领域的应用越来越广泛。对于本科生而言&#xff0c;撰写毕业论文不仅是学业的重要环节…

wsl2使用windows代理

1.查看wsl和windows版本号: wsl --version WSL 版本: 2.5.10.0 内核版本: 6.6.87.2-1 WSLg 版本: 1.0.66 MSRDC 版本: 1.2.6074 Direct3D 版本: 1.611.1-81528511 DXCore 版本: 10.0.26100.1-240331-1435.ge-release …

第六课:ORM 是什么?——从 JDBC 到 MyBatis / JPA 的一次认知升级

很多人学后端&#xff0c;会把 MyBatis / JPA 当成“查数据库的工具”。 但真正做过系统的人都会发现&#xff1a; &#x1f449; 数据库访问&#xff0c;从来不是“查数据”&#xff0c;而是一整套对象持久化体系。 这一篇不讲 API、不讲配置、不写教程。 只做一件事&#xff…

Spring 新声明式 HTTP 客户端:HTTP Interface + RestClient,把“调用外部 API”写成接口

1. 核心概念&#xff1a;HTTP Service Clients&#xff08;HTTP Interface&#xff09; 它的本质是两件事&#xff1a;(Home) 用接口定义“我要调用的 HTTP API”&#xff08;方法上标注 GetExchange 等&#xff09;用 HttpServiceProxyFactory 基于底层 HTTP 客户端&#xf…

java_ssm10二手汽车销售系统n7v1m

目录 具体实现截图系统概述技术架构核心功能模块系统特色应用价值 系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 具体实现截图 系统概述 Java_SSM10二手汽车销售系统是基于SSM&#xff08;SpringSpringMVC…

2026全国靠谱拍摄剪辑培训机构哪个比较好

作为一个从零基础转行做短视频行业的过来人,真的太懂大家想学拍摄剪辑,却被五花八门的教学机构搞懵的心情了!现在短视频、直播火得一塌糊涂,拍摄剪辑早就不是“兴趣爱好”,而是能变现、能转行的硬技能——我查过《…

Excel数据检测大师:ISBLANK与ISLOGICAL函数实战指南

如何从杂乱的数据中快速识别空白项和逻辑值&#xff1f;这两个函数能帮你建立数据质量检查的第一道防线&#xff01; 一、核心函数解析&#xff1a;数据质量的守门员 1. ISBLANK函数 - 空白检测专家 ISBLANK(单元格引用) 功能&#xff1a;判断指定单元格是否为空。如果是真正空…

java_ssm11办公电子政务管理系统 上下班考勤打卡系统

目录 具体实现截图办公电子政务管理系统与上下班考勤打卡系统摘要 系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 具体实现截图 办公电子政务管理系统与上下班考勤打卡系统摘要 办公电子政务管理系统是基于…

​工信部发布人形机器人标准化指南,博银合创落地工业具身智能实验室,Meta发布V-JEPA 2世界模型,博世与OpenAI深化合作

工信部发布人形机器人标准化体系指南&#xff0c;推动产业规模化发展 国新办发布会上&#xff0c;工信部明确宣布将正式印发《人形机器人与具身智能综合标准化体系建设指南》&#xff0c;同步加大国家人工智能产业基金扶持力度&#xff0c;推进开源社区建设&#xff0c;破解行…

2026年西安发泡混凝土厂家靠谱推荐陕西曲益存建筑工程有限公司-深耕发泡混凝土领域

发泡混凝土是一种通过在水泥浆料中引入大量均匀、封闭的气泡而制成的轻质多孔材料。它结合了混凝土的耐久性和泡沫的轻质性,在现代建筑和工程中应用广泛。一、西安发泡混凝土厂家陕西曲益存建筑工程有限公司top实力解…

让机器人拥有本能反应!清华开源:一套代码实现跑酷、野外徒步两大能力

清华大学交叉信息研究院与上海期智研究院联合推出的Project-Instinct框架&#xff0c;给出了一个新答案。 如何让机器人同时具备“本能反应”与复杂运动能力&#xff1f; 清华大学交叉信息研究院与上海期智研究院联合推出的Project-Instinct框架&#xff0c;给出了一个新答案…