题目描述

题目链接

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

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

子数组 是数组的连续子序列。

示例 1:

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

示例 2:

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

算法1

(暴力 + 前缀积优化) $O(n^2)$

如果使用通常的暴力算法,那么需要枚举区间的左端点,再枚举长度,这样的话需要的时间、空间复杂度都很高

那么此时可以想到,使用前缀积的思想来进行优化

与前缀和类似,可以预先处理出来数组的前缀积,再依次枚举区间的长度、端点就可以优化掉一维

算法2

(DP) $O(n)$

利用动态规划的思想:
很容易从题目描述中看出,选择每个点作为子数组终点时,代表的是一系列选择的结果,因此需要使用动态规划来表示一系列结果

由于本题中是求子数组的最大乘积,而乘法有着负负得正的性质,因此在每次更新状态时,要考虑到当前元素的正负性对答案的赢想,所以需要保存一个最小值,方便与负数相乘而得到最大的乘积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
vector<int> f(n);
vector<int> g(n);

f[0] = nums[0];
g[0] = nums[0];

int res = nums[0];
for(int i = 1; i < n; i ++)
{
f[i] = max(nums[i], max(f[i - 1] * nums[i], g[i - 1] * nums[i]));
g[i] = min(nums[i], min(f[i - 1] * nums[i], g[i - 1] * nums[i]));
res = max(res, f[i]);
}

return res;
}
};

算法3

(DP + 滚动数组) $O(n)$

在上一题中,能够发现所有的情况只会由上一个状态更新而来,而其余情况的存储是多余的,因此不需要使用数组来存储每种状态,可以使用两个变量来保存上一次情况的结果,从而更新下一次状态即可

经过滚动数组优化之后,可以将空间复杂度降为 $O(1)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();

int f = nums[0], g = nums[0], res = nums[0];

for(int i = 1; i < n; i ++)
{
int a = nums[i];
int fa = f * a, ga = g * a;
f = max(a, max(fa, ga));
g = min(a, min(fa, ga));
res = max(f, res);
}

return res;

}
};