LeetCode 152. 乘积最大子数组【动态规划+空间优化】中等
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
给你一个整数数组 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 * 10^4-10 <= nums[i] <= 10nums的任何子数组的乘积都 保证 是一个 32-位 整数
解法 动态规划
本题是 [[LeetCode 53. 最大子数组和【动态规划】简单]] 的乘法版本,推荐先完成 53 题。
寻找子问题
例如 n u m s = [ − 2 , 1 , − 3 , 4 ] nums=[−2,1,−3,4] nums=[−2,1,−3,4] ,讨论右端点为 n u m s [ 3 ] = 4 nums[3]=4 nums[3]=4 的子数组的最大乘积:
- 4 4 4 单独组成一个子数组。
- 4 4 4 和前面的子数组拼起来,也就是在右端点为 n u m s [ 2 ] = − 3 nums[2]=−3 nums[2]=−3 的乘积最大子数组之后添加 4 4 4 。
又例如 n u m s = [ − 2 , 1 , − 3 , − 4 ] nums=[−2,1,−3,−4] nums=[−2,1,−3,−4] ,讨论右端点为 n u m s [ 3 ] = − 4 nums[3]=−4 nums[3]=−4 的子数组的最大乘积:
- − 4 −4 −4 单独组成一个子数组。
- − 4 −4 −4 和前面的子数组拼起来,由于 − 4 −4 −4 是负数,要想得到最大的乘积,根据负负得正,我们可以在右端点为 n u m s [ 2 ] = − 3 nums[2]=−3 nums[2]=−3 的乘积最小子数组之后添加 − 4 −4 −4 。
状态定义与状态转移方程
上面两个例子启发我们,需要在遍历 n u m s nums nums 的同时,维护两个信息:
- 右端点下标为 i i i 的子数组的最大乘积,记作 f m a x [ i ] f_{max}[i] fmax[i] 。
- 右端点下标为 i i i 的子数组的最小乘积,记作 f m i n [ i ] f_{min}[i] fmin[i] 。
设 x = n u m s [ i ] x=nums[i] x=nums[i] ,分类讨论:
- x x x 单独组成一个子数组,那么 f m a x [ i ] = x f_{max}[i]=x fmax[i]=x 。
- x x x 和前面的子数组拼起来,也就是在右端点下标为 i − 1 i−1 i−1 的乘积最大子数组之后添加 x x x ,那么 f m a x [ i ] = f m a x [ i − 1 ] ⋅ x f_{max}[i]=f_{max} [i−1]⋅x fmax[i]=fmax[i−1]⋅x ;也可以在右端点下标为 i − 1 i−1 i−1 的乘积最小子数组之后添加 x x x ,那么 f m a x [ i ] = f m i n [ i − 1 ] ⋅ x f_{max}[i]=f_{min}[i−1]⋅x fmax[i]=fmin[i−1]⋅x 。
把这两种都算一下,这样我们就无需判断 x x x 到底是正还是负了。
三种情况取最大值,得
f
m
a
x
[
i
]
=
max
(
f
m
a
x
[
i
−
1
]
⋅
x
,
f
m
i
n
[
i
−
1
]
⋅
x
,
x
)
f_{max}[i]=\max(f_{max}[i−1]⋅x,f_{min}[i−1]⋅x,x)
fmax[i]=max(fmax[i−1]⋅x,fmin[i−1]⋅x,x)
同理得
f
m
i
n
[
i
]
=
min
(
f
m
a
x
[
i
−
1
]
⋅
x
,
f
m
i
n
[
i
−
1
]
⋅
x
,
x
)
f_{min}[i]=\min(f_{max}[i−1]⋅x,f_{min}[i−1]⋅x,x)
fmin[i]=min(fmax[i−1]⋅x,fmin[i−1]⋅x,x)
由于以
n
u
m
s
[
0
]
nums[0]
nums[0] 为右端点的子数组乘积只能是
n
u
m
s
[
0
]
nums[0]
nums[0] ,所以初始值为
f
m
a
x
[
0
]
=
f
m
i
n
[
0
]
=
n
u
m
s
[
0
]
f_{max}[0]=f_{min}[0]=nums[0]
fmax[0]=fmin[0]=nums[0]这是一种初始化的方法,下文会讲另外一种。
答案为 max ( f m a x ) \max(f_{max}) max(fmax) 。
写法1
class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
int[] fMax = new int[n];
int[] fMin = new int[n];
fMax[0] = fMin[0] = nums[0];
for (int i = 1; i < n; i++) {
int x = nums[i];
// 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,
// 或者单独组成一个子数组,只有 x 一个元素
fMax[i] = Math.max(Math.max(fMax[i - 1] * x, fMin[i - 1] * x), x);
fMin[i] = Math.min(Math.min(fMax[i - 1] * x, fMin[i - 1] * x), x);
}
return Arrays.stream(fMax).max().getAsInt();
}
}
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
vector<int> f_max(n), f_min(n);
f_max[0] = f_min[0] = nums[0];
for (int i = 1; i < n; i++) {
int x = nums[i];
// 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,
// 或者单独组成一个子数组,只有 x 一个元素
f_max[i] = max({f_max[i - 1] * x, f_min[i - 1] * x, x});
f_min[i] = min({f_max[i - 1] * x, f_min[i - 1] * x, x});
}
return ranges::max(f_max);
}
};
class Solution:
def maxProduct(self, nums: List[int]) -> int:
n = len(nums)
f_max = [0] * n
f_min = [0] * n
f_max[0] = f_min[0] = nums[0]
for i in range(1, n):
x = nums[i]
# 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,
# 或者单独组成一个子数组,只有 x 一个元素
f_max[i] = max(f_max[i - 1] * x, f_min[i - 1] * x, x)
f_min[i] = min(f_max[i - 1] * x, f_min[i - 1] * x, x)
return max(f_max)
func maxProduct(nums []int) int {
n := len(nums)
fMax := make([]int, n)
fMin := make([]int, n)
fMax[0], fMin[0] = nums[0], nums[0]
for i := 1; i < n; i++ {
x := nums[i]
// 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,
// 或者单独组成一个子数组,只有 x 一个元素
fMax[i] = max(fMax[i-1]*x, fMin[i-1]*x, x)
fMin[i] = min(fMax[i-1]*x, fMin[i-1]*x, x)
}
return slices.Max(fMax)
}
impl Solution {
pub fn max_product(nums: Vec<i32>) -> i32 {
let n = nums.len();
let mut f_max = vec![0; n];
let mut f_min = vec![0; n];
f_max[0] = nums[0];
f_min[0] = nums[0];
for i in 1..n {
let x = nums[i];
// 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,
// 或者单独组成一个子数组,只有 x 一个元素
f_max[i] = x.max(f_max[i - 1] * x).max(f_min[i - 1] * x);
f_min[i] = x.min(f_max[i - 1] * x).min(f_min[i - 1] * x);
}
*f_max.iter().max().unwrap()
}
}
var maxProduct = function(nums) {
const n = nums.length;
const fMax = new Array(n);
const fMin = new Array(n);
fMax[0] = fMin[0] = nums[0];
for (let i = 1; i < n; i++) {
const x = nums[i];
// 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,
// 或者单独组成一个子数组,只有 x 一个元素
fMax[i] = Math.max(fMax[i - 1] * x, fMin[i - 1] * x, x);
fMin[i] = Math.min(fMax[i - 1] * x, fMin[i - 1] * x, x);
}
return Math.max(...fMax);
};
#define MAX(a, b) ((b) > (a) ? (b) : (a))
#define MIN(a, b) ((b) < (a) ? (b) : (a))
int maxProduct(int* nums, int numsSize) {
int* f_max = malloc(numsSize * sizeof(int));
int* f_min = malloc(numsSize * sizeof(int));
f_max[0] = f_min[0] = nums[0];
int ans = nums[0];
for (int i = 1; i < numsSize; i++) {
int x = nums[i];
// 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,
// 或者单独组成一个子数组,只有 x 一个元素
f_max[i] = MAX(MAX(f_max[i - 1] * x, f_min[i - 1] * x), x);
f_min[i] = MIN(MIN(f_max[i - 1] * x, f_min[i - 1] * x), x);
ans = MAX(ans, f_max[i]);
}
free(f_max);
free(f_min);
return ans;
}
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n) ,其中 n n n 是 n u m s nums nums 的长度。
- 空间复杂度: O ( n ) O(n) O(n) 。
写法2 空间优化
由于计算 f m a x [ i ] f_{max}[i] fmax[i] 和 f m i n [ i ] f_{min}[i] fmin[i] 只会用到 f m a x [ i − 1 ] f_{max}[i - 1] fmax[i−1] 和 f m i n [ i − 1 ] f_{min}[i - 1] fmin[i−1] ,不会用到更早的状态,所以可用两个变量 f m a x f_{max} fmax 和 f m i n f_{min} fmin 滚动计算。
状态转移方程简化为:
f
m
a
x
=
max
(
f
m
a
x
⋅
x
,
f
m
i
n
⋅
x
,
x
)
f_{max} = \max( f_{max} \cdot x, f_{min} \cdot x, x)
fmax=max(fmax⋅x,fmin⋅x,x)
f
m
i
n
=
min
(
f
m
a
x
⋅
x
,
f
m
i
n
⋅
x
,
x
)
f_{min} = \min( f_{max} \cdot x, f_{min} \cdot x, x)
fmin=min(fmax⋅x,fmin⋅x,x)
注意这两个式子要同时计算。
代码实现时,可初始化 f m a x = f m i n = 1 f_{max} = f_{min} = 1 fmax=fmin=1 ,因为 1 1 1 乘以 n u m s [ 0 ] nums[0] nums[0] 等于 n u m s [ 0 ] nums[0] nums[0] ,这样我们可从下标 0 0 0 开始遍历 n u m s nums nums ,代码写起来更简单。
class Solution {
public int maxProduct(int[] nums) {
int ans = Integer.MIN_VALUE; // 注意答案可能是负数
int fMax = 1;
int fMin = 1;
for (int x : nums) {
int mx = fMax;
fMax = Math.max(Math.max(fMax * x, fMin * x), x);
fMin = Math.min(Math.min(mx * x, fMin * x), x);
ans = Math.max(ans, fMax);
}
return ans;
}
}
class Solution {
public:
int maxProduct(vector<int>& nums) {
int ans = INT_MIN; // 注意答案可能是负数
int f_max = 1, f_min = 1;
for (int x : nums) {
int mx = f_max;
f_max = max({f_max * x, f_min * x, x});
f_min = min({mx * x, f_min * x, x});
ans = max(ans, f_max);
}
return ans;
}
};
class Solution:
def maxProduct(self, nums: List[int]) -> int:
ans = -inf # 注意答案可能是负数
f_max = 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 ans
func maxProduct(nums []int) int {
ans := math.MinInt // 注意答案可能是负数
fMax, fMin := 1, 1
for _, x := range nums {
fMax, fMin = max(fMax*x, fMin*x, x),
min(fMax*x, fMin*x, x)
ans = max(ans, fMax)
}
return ans
}
impl Solution {
pub fn max_product(nums: Vec<i32>) -> i32 {
let mut ans = i32::MIN; // 注意答案可能是负数
let mut f_max = 1;
let mut f_min = 1;
for x in nums {
let mx = f_max;
f_max = x.max(f_max * x).max(f_min * x);
f_min = x.min(mx * x).min(f_min * x);
ans = ans.max(f_max);
}
ans
}
}
var maxProduct = function(nums) {
let ans = -Infinity; // 注意答案可能是负数
let fMax = 1, fMin = 1;
for (const x of nums) {
const mx = fMax;
fMax = Math.max(fMax * x, fMin * x, x);
fMin = Math.min(mx * x, fMin * x, x);
ans = Math.max(ans, fMax);
}
return ans;
};
#define MAX(a, b) ((b) > (a) ? (b) : (a))
#define MIN(a, b) ((b) < (a) ? (b) : (a))
int maxProduct(int* nums, int numsSize) {
int ans = INT_MIN; // 注意答案可能是负数
int f_max = 1, f_min = 1;
for (int i = 0; i < numsSize; i++) {
int x = nums[i];
int mx = f_max;
f_max = MAX(MAX(f_max * x, f_min * x), x);
f_min = MIN(MIN(mx * x, f_min * x), x);
ans = MAX(ans, f_max);
}
return ans;
}
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n) ,其中 n n n 是 n u m s nums nums 的长度。
- 空间复杂度: O ( 1 ) O(1) O(1) 。
写法3
class Solution {
public int maxProduct(int[] nums) {
int ans = Integer.MIN_VALUE;
int fMax = 1;
int fMin = 1;
for (int x : nums) {
if (x < 0) {
// 下面与 x 相乘后,最大的正数变成最小的负数,最小的负数变成最大的正数
// 提前交换,这样可以把 x < 0 和 x >= 0 的情况合并,合并后,计算 max 和 min 可以少一项
int tmp = fMax;
fMax = fMin;
fMin = tmp;
}
fMax = Math.max(fMax * x, x);
fMin = Math.min(fMin * x, x);
ans = Math.max(ans, fMax);
}
return ans;
}
}
class Solution {
public:
int maxProduct(vector<int>& nums) {
int ans = INT_MIN;
int f_max = 1, f_min = 1;
for (int x : nums) {
if (x < 0) {
// 下面与 x 相乘后,最大的正数变成最小的负数,最小的负数变成最大的正数
// 提前交换,这样可以把 x < 0 和 x >= 0 的情况合并,合并后,计算 max 和 min 可以少一项
swap(f_max, f_min);
}
f_max = max(f_max * x, x);
f_min = min(f_min * x, x);
ans = max(ans, f_max);
}
return ans;
}
};
class Solution:
def maxProduct(self, nums: List[int]) -> int:
ans = -inf
f_max = f_min = 1
for x in nums:
if x < 0:
# 下面与 x 相乘后,最大的正数变成最小的负数,最小的负数变成最大的正数
# 提前交换,这样可以把 x < 0 和 x >= 0 的情况合并,合并后,计算 max 和 min 可以少一项
f_max, f_min = f_min, f_max
f_max = max(f_max * x, x)
f_min = min(f_min * x, x)
ans = max(ans, f_max)
return ans
func maxProduct(nums []int) int {
ans := math.MinInt
fMax, fMin := 1, 1
for _, x := range nums {
if x < 0 {
// 下面与 x 相乘后,最大的正数变成最小的负数,最小的负数变成最大的正数
// 提前交换,这样可以把 x < 0 和 x >= 0 的情况合并,合并后,计算 max 和 min 可以少一项
fMax, fMin = fMin, fMax
}
fMax = max(fMax*x, x)
fMin = min(fMin*x, x)
ans = max(ans, fMax)
}
return ans
}
impl Solution {
pub fn max_product(nums: Vec<i32>) -> i32 {
let mut ans = i32::MIN;
let mut f_max = 1;
let mut f_min = 1;
for x in nums {
if x < 0 {
// 下面与 x 相乘后,最大的正数变成最小的负数,最小的负数变成最大的正数
// 提前交换,这样可以把 x < 0 和 x >= 0 的情况合并,合并后,计算 max 和 min 可以少一项
(f_max, f_min) = (f_min, f_max);
}
f_max = x.max(f_max * x);
f_min = x.min(f_min * x);
ans = ans.max(f_max);
}
ans
}
}
var maxProduct = function(nums) {
let ans = -Infinity;
let fMax = 1, fMin = 1;
for (const x of nums) {
if (x < 0) {
// 下面与 x 相乘后,最大的正数变成最小的负数,最小的负数变成最大的正数
// 提前交换,这样可以把 x < 0 和 x >= 0 的情况合并,合并后,计算 max 和 min 可以少一项
[fMax, fMin] = [fMin, fMax];
}
fMax = Math.max(fMax * x, x);
fMin = Math.min(fMin * x, x);
ans = Math.max(ans, fMax);
}
return ans;
};
#define MAX(a, b) ((b) > (a) ? (b) : (a))
#define MIN(a, b) ((b) < (a) ? (b) : (a))
int maxProduct(int* nums, int numsSize) {
int ans = INT_MIN;
int f_max = 1, f_min = 1;
for (int i = 0; i < numsSize; i++) {
int x = nums[i];
if (x < 0) {
// 下面与 x 相乘后,最大的正数变成最小的负数,最小的负数变成最大的正数
// 提前交换,这样可以把 x < 0 和 x >= 0 的情况合并,合并后,计算 MAX 和 MIN 可以少一项
int tmp = f_max;
f_max = f_min;
f_min = tmp;
}
f_max = MAX(f_max * x, x);
f_min = MIN(f_min * x, x);
ans = MAX(ans, f_max);
}
return ans;
}
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n) ,其中 n n n 是 n u m s nums nums 的长度。
- 空间复杂度: O ( 1 ) O(1) O(1) 。
变形题
把子数组改成子序列,要怎么做?
- [[2708. 一个小组的最大实力值]]
更多相似题目,见动态规划题单中的「§1.3 最大子数组和」。
更多推荐

所有评论(0)