乘积最大子数组
LeetCode 152. 乘积最大子数组 —— 动态规划详解(含正负状态)
问题描述
给定一个整数数组 nums,找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是连续子数组。
为什么需要两个状态?
与“最大子数组和”问题不同,乘积具有一个特殊的性质:负负得正。如果只记录到当前位置的最大乘积,当遇到负数时,可能会丢失潜在的最大值(因为一个很小的负数乘以另一个负数可能变成很大的正数)。
例如:nums = [-2, 3, -4]。
- 如果只记录最大值:
- i=0:最大值 = -2
- i=1:max(-2*3=-6, 3) = 3
- i=2:max(3(-4)=-12, -4) = -4 → 最终结果为 -4,但实际最大乘积是 (-2)3*(-4)=24。
因此,我们需要同时记录以当前位置结尾的乘积的最大值和最小值,因为最小值(负数)可能在遇到负数时变成最大值。
动态规划思路
定义两个状态:
dpmax[i]:表示以第i个元素结尾的乘积最大的连续子数组的乘积。dpmin[i]:表示以第i个元素结尾的乘积最小的连续子数组的乘积。
状态转移方程(考虑当前元素 nums[i] 可以单独成段,也可以与前面的子数组合并):
dpmax[i] = max(nums[i], dpmax[i-1] * nums[i], dpmin[i-1] * nums[i])
dpmin[i] = min(nums[i], dpmax[i-1] * nums[i], dpmin[i-1] * nums[i])
由于每一步只依赖于前一步的状态,我们可以用两个变量滚动更新,将空间复杂度降为 O(1)。但需要注意更新顺序:在计算 dpmin 时,dpmax 已经被更新了,所以需要先保存旧的 dpmax。
代码实现
class Solution:
def maxProduct(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return 0
# 初始化
dpmax = dpmin = res = nums[0]
for i in range(1, n):
# 保存旧的 dpmax,因为下面会立即更新
t = dpmax
dpmax = max(nums[i], dpmax * nums[i], dpmin * nums[i])
dpmin = min(nums[i], dpmin * nums[i], t * nums[i])
res = max(res, dpmax)
return res
代码解释
- 第 4 行:处理空数组的情况(虽然题目保证至少有一个数,但为了健壮性可以加上)。
- 第 6 行:初始化
dpmax、dpmin和全局结果res为第一个元素。 - 循环:从第二个元素开始遍历。
- 第 9 行:
t = dpmax保存旧的dpmax值,因为下一行dpmax会被覆盖,而dpmin的计算需要用到旧的dpmax(即t)。 - 第 10 行:更新
dpmax,考虑三种情况:当前元素单独成段、当前元素乘以上一个最大乘积、当前元素乘以上一个最小乘积(可能负负得正)。 - 第 11 行:更新
dpmin,同样考虑三种情况,但使用旧的dpmax(即t)而不是刚更新的dpmax,否则会导致逻辑错误(例如连续两个负数会互相影响)。 - 第 12 行:更新全局最大值。
- 第 9 行:
为什么更新 dpmin 时要使用旧的 dpmax?
假设我们不用 t,而是直接写:
dpmax = max(nums[i], dpmax * nums[i], dpmin * nums[i])
dpmin = min(nums[i], dpmax * nums[i], dpmin * nums[i])
此时第二行中的 dpmax 已经是更新后的值,这会导致 dpmin 错误地使用了新的 dpmax 去乘 nums[i],破坏了状态转移的正确性。因为状态转移方程要求使用的是前一个位置的 dpmax 和 dpmin,而不是当前更新后的值。
复杂度分析
- 时间复杂度:O(n),只需要遍历一次数组。
- 空间复杂度:O(1),只使用了常数个变量。
测试用例验证
[2,3,-2,4]→ 正确结果 6[-2,0,-1]→ 正确结果 0[-2,3,-4]→ 正确结果 24[0,2]→ 正确结果 2[-3,-1,-1]→ 正确结果 3(子数组[-3,-1]或[-1,-1])
总结
这道题的关键在于认识到乘积的正负转换特性,因此需要同时维护最大值和最小值两个状态。通过滚动变量优化空间,并注意保存旧值,就能写出简洁高效的解法。这种技巧也常见于其他需要考虑正负状态的动态规划问题中(如“最大子数组绝对值”等)。
希望这篇博客能帮助你彻底理解乘积最大子数组问题!如果有任何疑问,欢迎留言讨论。

浙公网安备 33010602011771号