乘积最大子数组 图解题解
这道题到底在问什么
- 输入
- nums=[2,3,-2,4,-1,2,1,-5,4,3]
- 输出
- 480
最优解:为什么这么做
一句话答案:LeetCode 152 乘积最大子数组的标准解是双状态动态规划:同时维护「以当前元素结尾」的最大乘积 imax 和最小乘积 imin。因为负负得正,眼下最小(最负)的乘积乘一个负数就可能翻身成最大,所以遇到负数先交换 imax 与 imin 再转移。一遍扫描即可,时间 O(n)、空间 O(1)。
乘积最大子数组难在哪
在数组 nums 里找一段连续子数组,使所有元素的乘积最大,返回这个乘积。数组里混着正数和负数(还可能有 0),这正是难点:加法世界里「越加越大」的直觉,在乘法世界被负号打碎——一个负数把大变小、把小变大,两个负数相乘又翻回正。比如 nums = [2,3,-2,4,-1,2,1,-5,4,3] 的答案是 480,靠的恰恰是负数之间的配合。
为什么照搬最大子数组和的套路会漏解
这题长得很像 LeetCode 53 最大子数组和,那题只维护一个状态:以当前元素结尾的最大和。照搬到乘法上——只维护「以当前元素结尾的最大乘积」——会立刻漏解:一段乘积是 -48 的前缀在「最大」的眼里一文不值,早被丢弃;可它后面若跟着一个 -1,乘起来就是 48,反而是全场希望最大的种子。
关键观察由此而来:乘法里「当前最小」和「当前最大」同样有前途,一个负数就能让两者互换身份。所以状态必须成对维护——最大乘积 imax 和最小乘积 imin 缺一不可,让最负的那条线也活着,等负数来接它翻身。
imax 和 imin 两个状态怎么定义
定义 imax 为「以当前元素结尾的连续子数组的最大乘积」,imin 为同一口径下的最小乘积,二者都强制「以当前元素结尾」——这保证子数组连续,也让下一个元素能直接接在后面。全局答案 ans 则是历史上所有 imax 中的最大者,因为最优子数组总得以某个位置收尾,逐位收集就不会错过。三个变量都用 nums[0] 初始化,从第二个元素起逐个转移。
遇到负数为什么要先交换再转移
对每个新元素 x,以 x 结尾的子数组只有两种形态:x 自己另起一段,或接在前一段后面。所以 imax = max(x, imax * x)、imin = min(x, imin * x)。但若 x 是负数,乘法会把大小关系整体翻转——原来最大的乘完变最小,原来最小的乘完变最大。先把 imax 与 imin 交换再套同一组转移式,等价于让 x 去乘「乘完它之后会变大的那一条」,两条候选线都不丢。交换必须发生在转移之前,写反了两个状态全错。
「和 x 本身比」这一半也不可省:遇到 0 或前段乘积方向不利时,max(x, imax * x) 允许从 x 重新起段,把拖后腿的前缀干净地甩掉,0 自然成为分段点。
复杂度多少,最容易错在哪
每个元素只做常数次比较和乘法,一遍扫描,时间 O(n);全程只有 imax、imin、ans 三个变量,空间 O(1)。
三个高频翻车点复盘:只维护 imax 不维护 imin,负负得正的翻身仗直接打不了;转移时忘了和 x 本身比,被 0 或反号的前缀拖死;交换写在转移之后,负数一来两个状态同时污染。记住一句话就够了——加法只用记最大,乘法要最大最小成对记,遇负先换位。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3核心一句:同时维护 imax(最大)和 imin(最小)两个状态,缺一不可。
- 4第 0 格只有它自己:以 nums[0] 结尾的最大、最小乘积都是 2。表三行:nums 固定,imax / imin 待填。
- 5nums[1]=3 是正数(非负),不交换:最大还接最大,最小还接最小。
- 6落子:imax=6、imin=3。两条路里——「自己单独成段」或「接上前一段」——接上前面那段(2×3)更大。答案更新到 6。
- 7遇到负数 -2:负 × 负 = 正,所以先把 imax 和 imin 交换——原来最小的(最负的)乘上这个负数,最有希望变成新的最大。
- 8落子:imax=-2、imin=-12。两条路里——「自己单独成段」或「接上前一段」——当前数 -2 自己另起一段更大。答案更新到 6。
- 9nums[3]=4 是正数(非负),不交换:最大还接最大,最小还接最小。
- 10落子:imax=4、imin=-48。两条路里——「自己单独成段」或「接上前一段」——当前数 4 自己另起一段更大。答案更新到 6。
- 11遇到负数 -1:负 × 负 = 正,所以先把 imax 和 imin 交换——原来最小的(最负的)乘上这个负数,最有希望变成新的最大。
- 12落子:imax=48、imin=-4。两条路里——「自己单独成段」或「接上前一段」——接上前面那段(-48×-1)更大。答案更新到 48。
- 13nums[5]=2 是正数(非负),不交换:最大还接最大,最小还接最小。
- 14落子:imax=96、imin=-8。两条路里——「自己单独成段」或「接上前一段」——接上前面那段(48×2)更大。答案更新到 96。
- 15nums[6]=1 是正数(非负),不交换:最大还接最大,最小还接最小。
- 16落子:imax=96、imin=-8。两条路里——「自己单独成段」或「接上前一段」——接上前面那段(96×1)更大。答案更新到 96。
- 17遇到负数 -5:负 × 负 = 正,所以先把 imax 和 imin 交换——原来最小的(最负的)乘上这个负数,最有希望变成新的最大。
- 18落子:imax=40、imin=-480。两条路里——「自己单独成段」或「接上前一段」——接上前面那段(-8×-5)更大。答案更新到 96。
- 19nums[8]=4 是正数(非负),不交换:最大还接最大,最小还接最小。
- 20落子:imax=160、imin=-1920。两条路里——「自己单独成段」或「接上前一段」——接上前面那段(40×4)更大。答案更新到 160。
- 21nums[9]=3 是正数(非负),不交换:最大还接最大,最小还接最小。
- 22落子:imax=480、imin=-5760。两条路里——「自己单独成段」或「接上前一段」——接上前面那段(160×3)更大。答案更新到 480。
- 23扫完整张表,所有 imax 里最大的就是答案 480(出现在第 9 格)。负数被 imin 接住、再翻身,正是这题的精髓。
⚠️ 容易写错的地方
✗ 错:只维护 imax
✓ 对:同时维护 imin
负数会让最小翻成最大,丢了 imin 就漏解
✗ 错:忘了和 x 本身比
✓ 对:imax=max(x, imax*x)
遇 0 或断点要能「重新起段」
✗ 错:交换写在转移之后
✓ 对:负数时先交换再转移
顺序反了 imax/imin 全错
完整代码(Python / C++ / Java)
Python
def maxProduct(nums):
imax = imin = ans = nums[0]
for x in nums[1:]:
if x < 0: imax, imin = imin, imax
imax = max(x, imax * x)
imin = min(x, imin * x)
ans = max(ans, imax)
return ansC++
int maxProduct(vector<int>& nums){
int imax = nums[0], imin = nums[0], ans = nums[0];
for(int i = 1; i < nums.size(); i++){
int x = nums[i];
if(x < 0) swap(imax, imin);
imax = max(x, imax * x);
imin = min(x, imin * x);
ans = max(ans, imax);
}
return ans;
}Java
int maxProduct(int[] nums){
int imax = nums[0], imin = nums[0], ans = nums[0];
for(int i = 1; i < nums.length; i++){
int x = nums[i];
if(x < 0){ int t = imax; imax = imin; imin = t; }
imax = Math.max(x, imax * x);
imin = Math.min(x, imin * x);
ans = Math.max(ans, imax);
}
return ans;
}复杂度
时间
O(n)
一遍线性扫描
空间
O(1)
只用 imax/imin/ans 三个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 乘积最大子数组 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么 max/min 都要和 x 本身比?+
遇到 0 或乘积变号时,需要从当前元素「重新起一段」,否则会被前面的 0/反号拖累。
和「最大子数组和」(LC53) 区别?+
加法只需维护最大;乘法因负负得正,必须额外维护最小,遇负交换。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 乘积最大子数组 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。