中等
给你一个整数数组 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 * 104-10 <= nums[i] <= 10nums的任何子数组的乘积都 保证 是一个 32-位 整数
📝 核心笔记:乘积最大子数组 (Maximum Product Subarray)
1. 核心思想 (一句话总结)
"双面间谍策略:同时维护'最大值'和'最小值',因为当前的'最小值'(负数)乘以一个负数,可能立马翻身变成'最大值'。"
- 状态定义:
-
fMax:以当前位置结尾的 最大 乘积。fMin:以当前位置结尾的 最小 乘积(为了等待下一个负数来实现逆袭)。
- 转移方程:由于负数的存在,最大值可能来自:
-
fMax * x(正数 正数)fMin * x(负数 负数 变成大正数)x本身(前面的积累太烂了,不如从头开始,相当于 Kadane 中的 restart)。
2. 算法流程 (DP 滚动数组)
- 初始化 (Init):
-
ans设为MIN_VALUE(或nums[0]),因为结果可能是负数。fMax和fMin初始化为 1(作为乘法的单位元,或者配合循环内的逻辑,第一次遇到x时会自动变成x)。
- 遍历 (Loop):
-
- 计算三个候选值:
fMax * x,fMin * x,x。 - 暂存 (Temp) :因为
fMax会先被更新,所以计算fMin时需要用到 更新前 的fMax,必须用变量mx暂存。 - 更新 (Update):
- 计算三个候选值:
-
-
fMax取三个候选中的最大值。fMin取三个候选中的最小值。
-
- 记录 (Record) :每次循环更新全局最大值
ans。
🔍 代码回忆清单
// 题目:LC 152. Maximum Product Subarray
class Solution {
public int maxProduct(int[] nums) {
int ans = Integer.MIN_VALUE; // 注意:结果可能是负数,不能设为 0
int fMax = 1;
int fMin = 1;
for (int x : nums) {
// 1. 暂存旧的 fMax
// 因为下面马上要更新 fMax,而计算 fMin 需要用到旧的 fMax
int mx = fMax;
// 2. 核心转移:在 (最大积*x, 最小积*x, x本身) 中找最大值
// 包含 x 意味着:如果前面的积是 0 或很小的负数,不如从当前 x 重新开始
fMax = Math.max(Math.max(fMax * x, fMin * x), x);
// 3. 维护最小值:为了应对可能的负负得正
// 注意这里用的是 mx (旧的 fMax)
fMin = Math.min(Math.min(mx * x, fMin * x), x);
// 4. 更新全局答案
ans = Math.max(ans, fMax);
}
return ans;
}
}
⚡ 快速复习 CheckList (易错点)
-
\] **为什么要维护** **fMin****?**
-
- 如果输入是
[-2, 3, -4]。 - 走到
3时,fMax是 3,fMin是 -6。 - 走到
-4时,3 * -4 = -12,但-6 * -4 = 24。如果没有维护fMin,就会漏掉 24 这个解。
- 如果输入是
-
\] **为什么要暂存** **mx****?**
-
- 在计算
fMin的时候,公式里需要old_fMax * x。但上一行代码已经把fMax更新了。如果不暂存,就会用新的fMax去算fMin,导致逻辑错误。
- 在计算
-
\] **fMax****和** **fMin****初始值设为 1 安全吗?**
-
- 对于这种写法是安全的。
- 假设数组是
[-2, ...]。第一轮循环:max(1*-2, 1*-2, -2)结果是 -2。 - 它利用了
Math.max(..., x)这一项,确保了第一个元素会被正确处理(相当于从第一个元素强制 Restart)。
🖼️ 数字演练
nums = [2, 3, -2, 4]
- Init :
ans = MIN,fMax = 1,fMin = 1. - x = 2:
-
mx = 1.fMax = max(2, 2, 2) = 2.fMin = min(2, 2, 2) = 2.ans = 2.
- x = 3:
-
mx = 2.fMax = max(6, 6, 3) = 6. (延续前面的)fMin = min(6, 6, 3) = 3.ans = 6.
- x = -2 (关键转折):
-
mx = 6.fMax = max(-12, -6, -2) = -2. (最大值变成了负数,或者选择重开)fMin = min(-12, -6, -2) = -12. (埋下伏笔,保留了一个很大的负数)ans = 6.
- x = 4:
-
mx = -2.fMax = max(-8, -48, 4) = 4. (因为之前的 fMin 乘 4 还是负数,所以不如从 4 重开)fMin = min(-8, -48, 4) = -48.ans = 6.
- Result: 6.
(注:如果数组是 [2, 3, -2, -4]**,最后一步 fMax**会变成 max(-2-4, -12*-4, -4) = 48**,体现* fMin**的作用)