题目
Given an integer array
nums
, find the contiguous subarray within an array (containing at least one number) which has the largest product.
Example 1:
Input: [2,3,-2,4] Output: 6 Explanation: [2,3] has the largest product 6.
Example 2:
Input: [-2,0,-1] Output: 0 Explanation: The result cannot be 2, because [-2,-1] is not a subarray.
分析
这道题的意思是让我们计算出一个整数数组中乘积最大的子序列的乘积,要注意子序列必须是连续的。
这道题的难点在于负数的存在,可以让一个本来乘积为负数的子序列在增加一个元素后乘积为正,比如-2, 3, 4, 5, -3
这个数组,如果没有最后的-3的话,答案就是60,子序列不能包含-2。但由于-3的存在,答案是360,子序列是整个数组。
这道题在leetcode中被划分为动态规划问题。
解法一
既然这是一个动态规划问题,那首先我们就需要一个数组max[n]来记录以第i个数结尾的子序列的最大乘积(0 <= i < n
),类似于最大子序列和的问题。
那么对于max[i]
,我们如何通过max[i - 1]
来计算出max[i]
呢?可能会想到类似于最大子序列和的解法,用max(max[i - 1] * nums[i], nums[i])
来求解,即最大乘积要么就是第i个数,要么就是第i个数乘以原来的乘积。
这种解法是无法解决负数存在的问题的,比如上面那个例子。貌似我们不能从max[3](5结尾)
来计算出max[4](-3结尾)
的值,它们并没有直接的关系,因为max[3]
没有把-2计算进去,而max[4]
需要把-2计算进去。
如果我们不能通过上一个子问题的结果来计算下一个子问题的话,好像就不是动态规划了,因此肯定是计算max[i]
的方法不对。
我们可以发现,-3会使结果变化的原因是前面子序列乘积的最小值是个负数,而且其绝对值是最大的乘积,因此新的结果就是原来乘积的最小值乘以-3。
想到这里就可以知道答案了,我们需要为以第i个数结尾的子序列维护最小乘积和最大乘积,这两个值同时用来计算以下一个数结尾的子序列的最小乘积和最大乘积。
代码
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
int min[n], max[n];
min[0] = max[0] = nums[0];
//min[i],max[i]分别表示以第i个数结尾的子序列的最小/最大值
for (int i = 1; i < n; ++i) {
min[i] = std::min(std::min(min[i - 1] * nums[i], nums[i]), max[i - 1] * nums[i]);
max[i] = std::max(std::max(max[i - 1] * nums[i], nums[i]), min[i - 1] * nums[i]);
}
int result = max[0];
for (int i = 1; i < n; ++i)
if (result < max[i]) result = max[i];
return result;
}
};
解法二
同样地,我们可以为这个DP算法优化空间复杂度,不难发现,我们可以把记录子序列最小/最大值的min,max数组优化成单个元素,因为每次计算min[i]
时要用到的都是min[i - 1]
和max[i - 1]
。
如果只用min
来存以第i个数结尾的子序列的最小值的话,当我们在计算出min[i]
之前,实际上min
里存的是min[i - 1]
的值,即上一轮循环的结果还没有被更新,可以用来计算这一轮的结果
要注意当计算出min
之后,上一轮的min
值就被覆盖了,而计算这一轮的max
需要用到上一轮的min
值,因此需要一个临时变量来存储上一轮的min
值
优化后算法空间复杂度O(1),时间复杂度O(n)
代码
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
int result;
int min, max;
result = min = max = nums[0];
//min,max分别表示以第i个数结尾的子序列的最小/最大值
for (int i = 1; i < n; ++i) {
int tmp = min;
min = std::min(std::min(min * nums[i], nums[i]), max * nums[i]);
max = std::max(std::max(max * nums[i], nums[i]), tmp * nums[i]);
if (result < max) result = max;
}
return result;
}
};
总结
这道题也是很典型的动态规划问题,难点在于由于负数的存在,最小乘积也是会影响到结果的,而我们一般只会想到要维护最大乘积的值,就比较难求解。