LeetCode 152. 乘积最大子数组
📌 题目描述
题目级别:中等
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续 子数组 (该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
-
示例 1:
输入:
nums = [2,3,-2,4]输出:
6解释: 子数组
[2,3]有最大乘积6。 -
示例 2:
输入:
nums = [-2,0,-1]输出:
0解释: 结果不能为 2, 因为
[-2,-1]不是子数组(不连续)。
💡 破题思路:动态规划的"负负得正"陷阱
这道题和"最大子数组和"非常像,但乘法有一个致命的特殊性:负号的存在 。
一个极其小的负数,如果再乘上一个负数,会瞬间翻盘变成一个巨大的正数!
因此,我们不能只记录"当前的最大乘积",我们还必须同时记录"当前的最小乘积"(因为它有潜力在遇到下一个负数时变身成最大乘积)。
本解法的极客高光点 (Swap 魔法):
常规思路是在每次遍历时,同时用 nums[i]、nums[i] * max_dp、nums[i] * min_dp 三个数去求新的最大值和最小值,代码写起来非常繁琐。
但我们回归数学本质:当遇到一个负数时,乘积的大小关系会发生反转 。
所以,如果我们发现当前数字 nums[i] < 0,我们直接把维护的"当前最大值 mam"和"当前最小值 mim"互换位置 (swap) !
互换之后,我们就可以像处理正数一样,毫无顾忌地直接更新 mam 和 mim 了。这个思路极其优雅,堪称神来之笔。
💻 C++ 代码实现 (O(1) 空间极简版)
cpp
class Solution {
public:
int maxProduct(vector<int>& nums) {
// mim 维护以当前元素结尾的最小乘积
// mam 维护以当前元素结尾的最大乘积
int mim = 1, mam = 1;
int res = INT_MIN; // 全局最大结果
for (int i = 0; i < nums.size(); i ++ )
{
// 绝妙逻辑:遇到负数,乘积的极值属性反转,直接 swap!
if (nums[i] < 0)
{
swap(mim, mam);
}
// 要么是从前面连乘过来的,要么就是从当前元素重新开始自己单干
mim = min(nums[i], mim * nums[i]);
mam = max(nums[i], mam * nums[i]);
// 每次更新全局最大值
res = max(res, mam);
}
return res;
}
};