跳到主要内容

0152. 乘积最大子数组

题目描述​

给你一个整数数组 nums,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

示例 1:

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

解题思路​

第一步:为什么不是最大子数组和的翻版?​

53. 最大子数组和 只需要维护以当前元素结尾的最大和。

但乘积不同:负数乘以负数会变成正数,而且正是最大值。所以最小值在下一秒可能变成最大值。

第二步:暴力解法​

枚举所有子数组,计算乘积:

# 枚举所有子数组:外层定起点、内层向右扩展,边乘边更新答案
def maxProduct(nums):
n = len(nums)
# 子数组至少要含一个数,所以单个元素也是候选,用它作初值
ans = nums[0]
for i in range(n):
# prod 表示「从 i 到当前 j」这一段连续子数组的乘积
prod = 1
for j in range(i, n):
# 每往右走一步就多乘一个元素,得到一个新的子数组
prod *= nums[j]
# nums[i..j] 这一段已经乘出来了,用它更新答案
ans = max(ans, prod)
return ans
  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)

第三步:动态规划​

同时维护两个状态:

  • f_max:以当前位置结尾的子数组的最大乘积
  • f_min:以当前位置结尾的子数组的最小乘积

为什么需要 f_min?因为当前元素如果是负数,f_min * x 可能变成最大值。

状态转移:

f_max = max(f_max * x, f_min * x, x)
f_min = min(f_max * x, f_min * x, x)

注意:Python 中可以通过元组解包同时更新两个值。


完整代码实现​

算法可视化 · 逐帧回放这道题的代码5 帧 · 录制输入 [2,3,-2,4]
from typing import List
from math import inf

class Solution:
"""
152. 乘积最大子数组 - 动态规划

核心思想:
由于负数的存在,最小值可能转瞬变成最大值。
所以需要同时维护以当前位置结尾的最大乘积和最小乘积。

时间复杂度:O(n)
空间复杂度:O(1)
"""

def maxProduct(self, nums: List[int]) -> int:
ans = -inf
f_max = 1
f_min = 1

for x in nums:
# 同时更新最大和最小乘积
f_max, f_min = max(f_max * x, f_min * x, x), \
min(f_max * x, f_min * x, x)
ans = max(ans, f_max)

return int(ans)

示例推演​

以 nums = [2, 3, -2, 4] 为例:

元素f_maxf_minans
2max(2, 2, 2) = 2min(2, 2, 2) = 22
3max(6, 6, 3) = 6min(6, 6, 3) = 36
-2max(-12, -6, -2) = -2min(-12, -6, -2) = -126
4max(-8, -48, 4) = 4min(-8, -48, 4) = -486

最终答案:6(子数组 [2, 3])

再试 nums = [-2, 0, -1]:

元素f_maxf_minans
-2-2-2-2
0000
-1max(0, 0, -1) = 0min(0, 0, -1) = -10

最终答案:0


复杂度分析​

解法时间复杂度空间复杂度说明
暴力O(n²)O(1)枚举所有子数组
动态规划O(n)O(1)最优解

易错点总结​

1. 要同时维护最小值​

如果只维护最大值,遇到负数时会出错(负负得正)。

2. 初始值​

ans 初始化为 -inf,因为数组中可能全是负数,最大乘积也是负数。

3. 同时更新​

# 右边两个表达式先全部读完再赋值,所以 min 用到的仍是上一轮的 f_max
f_max, f_min = max(...), min(...)

这样写能确保 f_min 使用的是上一轮的值,而不是已经被更新过的 f_max。

4. 0 的处理​

遇到 0 时,f_max 和 f_min 都会变成 0,相当于从 0 后面重新开始计算。


扩展思考​

如果要求输出子数组呢?​

除了维护最大/最小值,还需要记录对应的子数组起始位置。

为什么这题不能用单调栈?​

因为乘积的单调性比求和复杂得多,负数会改变单调性方向。

相关题目​

自测​

加载评论中...