乘积最大子数组
题目概括
给定一个整数数组 nums
,找出数组中乘积最大的非空连续子数组(至少包含一个数字),返回其对应乘积。要求结果在 32 位整数范围内。
示例:
输入:nums = [2,3,-2,4]
输出:6
解释:子数组 [2,3] 的乘积是 6,其余子数组乘积均小于 6。
算法思想
动态规划(双状态维护)
核心问题:负数的存在可能使最小值翻转为最大值(如 -5 * -3 = 15
)。
- 维护两个状态:
f_max
:以当前元素结尾的子数组的最大乘积f_min
:以当前元素结尾的子数组的最小乘积
- 对每个元素
x
,计算三种可能性:- 延续前序子数组(
f_max * x
) - 前序最小乘积可能翻转为最大(
f_min * x
) - 以当前元素重新开始(
x
)
- 延续前序子数组(
关键点
- 遇到负数时,交换
f_max
和f_min
的值(或通过同时计算实现等效效果) - 始终维护全局最大值
ans
算法步骤
-
初始化:
ans
设为负无穷(处理全负数或含零的情况)f_max = f_min = 1
(空乘积初始值)
-
遍历数组:
- 对每个元素
x
:- 计算候选值:
temp_max = f_max * x
,temp_min = f_min * x
- 更新当前状态:
f_max = max(temp_max, temp_min, x) # 三者取最大 f_min = min(temp_max, temp_min, x) # 三者取最小
- 更新全局最大值:
ans = max(ans, f_max)
- 计算候选值:
- 对每个元素
-
返回结果:
ans
具体代码
class Solution:
def maxProduct(self, nums: List[int]) -> int:
ans = -float('inf') # 初始化全局最大值
f_max = f_min = 1 # 空乘积初始值
for x in nums:
# 同时计算候选值,避免顺序影响
candidates = (f_max * x, f_min * x, x)
f_max = max(candidates) # 更新当前最大乘积
f_min = min(candidates) # 更新当前最小乘积
ans = max(ans, f_max) # 维护全局最大值
return ans
时间复杂度
-
时间复杂度: O ( n ) O(n) O(n)
仅需一次线性遍历,每个元素处理时间为 O ( 1 ) O(1) O(1)。 -
空间复杂度: O ( 1 ) O(1) O(1)
仅使用常数级别的额外空间(f_max
,f_min
,ans
)。
示例解析
案例1:nums = [2, 3, -2, 4]
遍历过程:
x | f_max | f_min | ans |
---|---|---|---|
2 | 2 | 2 | 2 |
3 | 6 | 3 | 6 |
-2 | -2 | -6 | 6 |
4 | 4 | -8 | 6 |
结果: 6
案例2:nums = [-2, 0, -1]
遍历过程:
x | f_max | f_min | ans |
---|---|---|---|
-2 | -2 | -2 | -2 |
0 | 0 | 0 | 0 |
-1 | 0 | -1 | 0 |
结果: 0
为什么需要维护最小值?
当遇到负数时,之前的 f_min
(可能为负数)与之相乘会变成正数,可能成为新的最大值。例如:
nums = [-3, -4]
处理第一个 -3:f_max = -3, f_min = -3
处理第二个 -4:
- temp_max = (-3) * (-4) = 12
- temp_min = (-3) * (-4) = 12
- f_max = max(12, 12, -4) = 12
最终结果为12
为什么需要与自身比较(x
)?
在动态规划解法中,f_max
和 f_min
表示以当前元素 x
结尾的子数组的最大和最小乘积。在状态转移时,我们需要考虑以下三种可能性:
- 延续前序子数组:
将x
与前序子数组的乘积相乘(f_max * x
或f_min * x
)。 - 以
x
重新开始:
丢弃前面的所有元素,单独以x
作为新的子数组(即子数组只包含x
)。
为何必须考虑 x
自身?
场景1:前序乘积为负数,当前元素为正数
- 示例:
nums = [-2, 3]
- 处理到
3
时,前序最大乘积是-2
。 - 若延续前序子数组:乘积为
-2 * 3 = -6
。 - 若单独以
3
开始:乘积为3
。 - 最终选择
3
,此时必须将x
纳入比较。
- 处理到
场景2:前序乘积为0,当前元素较大
- 示例:
nums = [0, 5]
- 处理到
5
时,前序乘积为0
。 - 延续前序乘积:
0 * 5 = 0
。 - 单独以
5
开始:乘积为5
。 - 最终选择
5
。
- 处理到
场景3:当前元素是唯一选项
- 示例:
nums = [-3]
- 由于子数组必须非空,唯一选择是
-3
。 - 必须将
x
自身作为候选值。
- 由于子数组必须非空,唯一选择是