题目描述:给你一个整数数组 nums
,请你找出数组中乘积最大的非空连续
子数组
(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
leetcode原题:https://leetcode.cn/problems/maximum-product-subarray/description/
首先,很容易想到暴力遍历的方法,也就是将所有的子数组的乘积全部算出来,取最大的即可。
那我们要明确什么是子数组,什么是子串?因为这决定了能否用暴力的方法。
子数组就是必须连续出现在原数组内的数组,而子串是可以不连续的。
题目中给定的数组长度是1e4级别的,如果是子数组,则共有(1+2+3+...+n) ,O(N^2)级别,若是子串,则共有[Cn1 + Cn2 + Cn3 +... + Cnn] = 2 ^N级别,这是一定会TLE的。
在确定可以用暴力的方法之后,我们要知道如何暴力,如果单纯的用一个双层for循环遍历子数组O(N^2)的方法去遍历,那么再加上算积的复杂度O(N),则变成了O(N^3)级别的,也会TLE。
所以我们尝试把算积的复杂度去掉,想到了“前缀积”的方法。
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
int res = nums[0];
vector<int> pre(n);
pre[0] = nums[0];
for(int i = 1; i < n; ++ i)
{
pre[i] = pre[i - 1] * nums[i];
}
for(int i = 0; i < n; ++ i)
{
for(int j = 1; j <= n; ++ j)
{
if(i - j >= 0 && pre[i - j])
res = max(res, pre[i] / pre[i - j]);
else
res = max(res, pre[i]);
}
}
return res;
}
};
但是,经过提交发现一个问题,那就是如果原数组nums中出现了一个0,我们的前缀积思想就会失效。
这里会发现,只要突然出现一个0,前缀积数组pre之后所有的项都成了0,这不是简单的加一个res = max{nums[i]}就可以解决的,这个判断仅在这个例子中有效,如果是{1,2,3,0,5,6}呢,这个判断就没有用了。
而面对这种情况,我们应该在遇到0的时候对0左右部分进行分开处理;
对0左边的部分,将其看成一个完成的数组,进行上面的两个for循环筛选出最大子数组的步骤。
对0右边的部分,应该将其重新定为一个新的数组,进行前缀积的操作。
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
int res = nums[0];
int m = 0;//m表示该段子数组首个非零元素的下标
vector<int> pre(n);
pre[0] = nums[0];
for(int i = 0; i < n; ++ i)
{
if(!nums[i])
{
res = max(res, nums[i]);
for(int j = m; j < i; ++ j)
{
for(int k = 1; k <= i - m; ++ k)
{
if(j - k >= 0 && pre[j - k])
{
res = max(res, pre[j] / pre[j - k]);
}
else
res = max(res, pre[j]);
}
}
m = i + 1;
continue;
}
else
{
pre[i] = nums[i];
if(i > 0 && pre[i - 1])
pre[i] *= pre[i - 1];
}
}
//对最后一段子数组进行处理
for(int i = m; i < n; ++ i)
{
for(int j = 1; j <= n - m; ++ j)
{
if(i - j >= 0 && pre[i - j])
res = max(res, pre[i] / pre[i - j]);
else
res = max(res, pre[i]);
}
}
return res;
}
};
但是发现这样做超时了,所以进行常数优化处理,把循环厘米不必要的去掉。
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
int res = nums[0];
int m = 0;//m表示该段子数组首个非零元素的下标
vector<int> pre(n);
pre[0] = nums[0];
for(int i = 0; i < n; ++ i)
{
if(!nums[i])
{
res = max(res, nums[i]);
for(int j = m; j < i; ++ j)
{
for(int k = 1; k <= 1 + j - m; ++ k)
{
if(j - k >= 0 && pre[j - k])
{
res = max(res, pre[j] / pre[j - k]);
}
else
res = max(res, pre[j]);
}
}
m = i + 1;
continue;
}
else
{
pre[i] = nums[i];
if(i > 0 && pre[i - 1])
pre[i] *= pre[i - 1];
}
}
//对最后一段子数组进行处理
for(int i = m; i < n; ++ i)
{
for(int j = 1; j <= 1 + i - m; ++ j)
{
if(i - j >= 0 && pre[i - j])
res = max(res, pre[i] / pre[i - j]);
else
res = max(res, pre[i]);
}
}
return res;
}
};
但还是卡了最后一个点,说明这题压根不想让你用暴力。
所以,换贪心的思路。
之所以想到用贪心是因为,这个最大的子数组乘积只能由三条路来,1.直接就是本身nums[i],2.需要前面的乘积最大值乘以nums[i],让它变得更大,3.需要用前面乘积的最小值乘以nums[i],负负得正得到最大的乘积。
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
int res = nums[0];
int premax = nums[0];
int premin = nums[0];
for(int i = 1; i < n; ++ i)
{
int curmax = max(nums[i], max(premax * nums[i], premin * nums[i]));
int curmin = min(nums[i], min(premin * nums[i], premax * nums[i]));
res = max(res, curmax);
premax = curmax;
premin = curmin;
}
return res;
}
};
这样用ON的规模就可以解决这道题了