题号 152
题目描述
给你一个整数数组 nums
,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
解题思路一——暴力法
对于给定数组 nums 中的每个元素 nums[ i ],分别计算:
nums[ 0 ] ~ nums[ i ] 所有元素的乘积
nums[ 1 ] ~ nums[ i ] 所有元素的乘积
……
nums[ i - 1 ] 和 nums[ i ] 的乘积
nums[ i ] 自身的值
从上述所有结果中,挑选值最大的存入数组 dp[ i ] 中,dp[ i ] 代表以元素 nums[ i ] 结尾的连续子数组的最大乘积值。
在 nums 中所有元素都被遍历完成后,从数组 dp 挑选数值最大值就是最终结果。
代码如下:
class Solution {
public:
int maxProduct(vector<int>& nums) {
int len = nums.size();
if (len == 0) return 0;
if (len == 1) return nums[0];
vector<int> dp(len); //数组dp用于记录以元素nums[i]结尾的连续子数组乘积的最大值
dp[0] = nums[0];
for (int i = 1; i < len; i++) {
int max = nums[i];
int mid = nums[i];
for (int j = i - 1; j >= 0; j--) {
mid = mid * nums[j];
if (mid > max)
max = mid;
}
dp[i] = max;
}
return *max_element(dp.begin(), dp.end());
}
};
时间复杂度:O(n^2)
空间复杂度:O(n)
解题思路二——动态规划
初始想法:
数组 dp[ i ] 用于记录以 nums[ i ] 结尾来的连续子数组乘积的最大值,考虑如何从 dp[ i - 1 ] 推导出 dp[ i ]:
(1)如果 nums[ i ] > 0,dp[ i - 1 ] > 0,则 dp[ i ] = nums[ i ] × dp[ i - 1];
(2)如果 nums[ i ] > 0,dp[ i - 1 ] <= 0, 则 dp[ i ] = nums[ i ];
(3)如果 nums[ i ] == 0,则 dp[ i ] = nums[ i ];
(4)如果 nums[ i ] < 0,dp[ i - 1 ] > 0,则 dp[ i ] = nums[ i ];
(5)如果 nums[ i [ < 0,dp[ i - 1] <= 0,则 dp[ i ] = nums[ i ] × dp[ i - 1 ]。
综上,dp[ i ] = max( nums[ i ] × dp[ i - 1 ],nums[ i ])。
但是,这种想法是错误的,因为不满足【最优子结构】的性质。
举个栗子:
nums = { -2, 3, -4 }
按照上述方法计算得到的 dp 数组为: dp = { -2, 3, -4 }
但是对于元素 4 而言,其乘积最大的连续子数组是 { -2,3,-4} ,结果为 24,而不是按照上述方法计算得到的 4 。
正式思路:
如果 nums[ i ] 是正数,则我们希望以 nums[ i - 1 ] 结尾的连续子数组的乘积也为正数,并且越大越好;
如果 nums[ i ] 是负数,则我们希望以 nums[ i - 1 ] 结尾的连续子数组的乘积也为负数,并且越小越好(负负得正,这样得到的乘积才最大)
所以,我们考虑维护两个数组:
数组 dp_max[ i ] 用于存储以 nums[ i ] 结尾的连续子数组的乘积的最大值;
数组 dp_min[ i ] 用于存储以 nums[ i ] 结尾的连续子数组的乘积的最小值;
推导公式:
dp_max[ i ] = max( nums[ i ] × dp_max[ i - 1 ],nums[ i ] × dp_min[ i - 1 ],nums[ i ] )
dp_min[ i ] = min( nums[ i ] × dp_min[ i - 1],nums[ i ] × dp_min[ i - 1 ],nums[ i ] )
问题的底:
dp_max[ 0 ] = nums[ 0 ],dp_min[ 0 ] = nums[ 0 ]
代码如下:
class Solution {
public:
int maxProduct(vector<int>& nums) {
int len = nums.size();
vector<int> dp_max(len), dp_min(len);
dp_max[0] = nums[0], dp_min[0] = nums[0];
for (int i = 1; i < len; i++) {
dp_max[i] = max(dp_max[i - 1] * nums[i], max(dp_min[i - 1] * nums[i], nums[i]));
dp_min[i] = min(dp_max[i - 1] * nums[i], min(dp_min[i - 1] * nums[i], nums[i]));
}
return *max_element(dp_max.begin(), dp_max.end());
}
};
时间复杂度:O(n)
空间复杂度:O(n)
改进:
上方代码中,我们使用数组 dp_max 和 dp_min 分别存放最大子数组的最大乘积和最小乘积。
由于题目只需要最终结果,我们可以使用 变量 来进行存储和更新,可以将空间复杂度降低至 O(1)
改进代码如下:
class Solution {
public:
int maxProduct(vector<int>& nums) {
int len = nums.size();
int dp_max, dp_min, ans;
dp_max = nums[0], dp_min = nums[0], ans = nums[0];
for (int i = 1; i < len; i++) {
dp_max = max(dp_max * nums[i], max(dp_min * nums[i], nums[i]));
dp_min = min(dp_max * nums[i], min(dp_min * nums[i], nums[i]));
ans = max(ans, dp_max);
}
return ans;
}
};
参考:乘积最大子数组