不能只用最大值相乘,因为负负得正、零清空乘积、乘积增长快,需同时维护当前最大积和最小积,通过滚动更新maxSoFar和minSoFar实现O(n)时间复杂度。

为什么不能只用最大值相乘?
找积最大的子数组和找和最大的子数组逻辑完全不同,因为负负得正、零会清空乘积、且乘积增长极快。单纯记录当前最大值会漏掉「前面有两个负数,中间夹着一个正数」这种关键组合。比如 [-2, 3, -4],整个数组积是 24,但若按贪心只保留正数或跳过负数,就得不到结果。
- 负数出现奇数次时,最大积可能来自去掉最左边或最右边那个负数的后缀/前缀
- 遇到
0必须重置状态,因为任何包含0的子数组积都是0 - 必须同时维护「当前最大积」和「当前最小积」:最小积可能是负数,下一个负数来时它就翻成最大积
用两个变量滚动更新 maxSoFar 和 minSoFar
这是最常用也最稳妥的做法,时间 O(n),空间 O(1),不依赖额外容器。
- 每次遍历新元素
nums[i],基于上一轮的maxSoFar和minSoFar计算三个候选值:nums[i]、maxSoFar <em> nums[i]</em>、minSoFar nums[i] - 新的
maxSoFar取三者最大,新的minSoFar取三者最小 - 全局答案在每次更新后取
maxSoFar的历史最大值
int maxProduct(vector<int>& nums) {
if (nums.empty()) return 0;
int maxSoFar = nums[0], minSoFar = nums[0], result = nums[0];
for (int i = 1; i < nums.size(); ++i) {
int tmp = maxSoFar;
maxSoFar = max({nums[i], maxSoFar * nums[i], minSoFar * nums[i]});
minSoFar = min({nums[i], tmp * nums[i], minSoFar * nums[i]});
result = max(result, maxSoFar);
}
return result;
}
注意:必须用临时变量保存旧的 maxSoFar,否则 minSoFar 更新时用的是已覆盖的新值,逻辑就错了。
遇到 0 时要不要清空?
要,但不是“清空”,而是重置为 nums[i] 本身。因为子数组必须连续,一旦断在 0,新子数组只能从 0 或其后开始。
立即学习“C++免费学习笔记(深入)”;
- 当
nums[i] == 0时,maxSoFar和minSoFar都设为0 - 下一轮如果
nums[i+1]是负数,minSoFar就能立刻捕获它,为后续翻盘留可能 - 不要跳过
0或设为1—— 那会破坏连续性,也混淆了“以当前位置结尾”的语义
边界和特殊输入怎么处理?
- 空数组:按题意通常返回
0 或抛异常,代码里先判空
- 单元素:直接返回该元素,
maxSoFar/minSoFar 初始化即覆盖
- 全负数组(如
[-2,-3,-4]):最大积是 -2(长度为 1),算法自然支持,无需特判
- 大数溢出:C++ 默认不检查,如果题目要求防溢出,需改用
long long 中间计算,但最终返回仍为 int;实际面试中一般假设不溢出
0 或抛异常,代码里先判空maxSoFar/minSoFar 初始化即覆盖[-2,-3,-4]):最大积是 -2(长度为 1),算法自然支持,无需特判long long 中间计算,但最终返回仍为 int;实际面试中一般假设不溢出真正容易被忽略的是:这个算法求的是「连续子数组」,不是子序列;且它不记录起止位置——如果需要输出具体子数组,得额外维护索引变量,每次更新 maxSoFar 时同步更新左右边界。


















