Find the contiguous subarray within an array (containing at least one number) which has the largest product.
For example, given the array [2,3,-2,4],
the contiguous subarray [2,3] has the largest product = 6.
s思路:
1. 先假设一些理想情况,当都是正数,最大积就是全部数相乘;如果有偶数个负数,也是全部相乘;如果有奇数个负数,那就要比较大小了,例如:nums=[-5,2,3,2-2,-4,2],就需要比较最左边的负数与它之前的正数之积,即:-5,和最右边的负数与它之后的正数之积,即:-4*2=-8,谁绝对值大,就那一边。这里就是:-5之后的所有数相乘即是最大积。
2. 目前为止,我们只讨论了正负数,还没有讨论0的情况。如果含有0,如何处理呢?想了一下,0的作用就是把这个序列分割成左右两个array。所以,需要分别按照上面的方法求最大值,然后比较选出较大的最大值即可!
3. 也就是说,这个题有o(n)的解法,因为每个数最多访问一次即可!
4. 上面的方法是可行,但写起来很复杂,也不好理解。我们换一个角度重新思考。从左往右遍历,我们计算包含当前正在遍历的数的最大乘积和最小乘积,为什么要最小值呢?因为最小值,如果是负数,那么乘以一个负数,就是最大值了。但这个最小值和最大值每次和当前的数相乘是注意,如果当前数是正数,就是最大值乘以当前值仍然是最大值,最小值乘以当前值仍是最小值;如果当前值是负数,那么最大值就是之前的最小值乘这个负数,最小值就是之前的最大值乘这个负数;这还没完,关键的一步是:得到最大最小值,还需要检查是否最大最小,如果当前的遍历的数比最大值还大或比最小值还小,那么就要更新最大值和最小值了,保证是最大值。
5. 上面的思路就是DP思路,就是一路记录包含当前遍历的数的最大乘积和最小乘积,然后取最大乘积的最大值即可!
num | -5 | 2 | 3 | -2 | -4 | 2 |
---|---|---|---|---|---|---|
curmx | -5 | 2 | 6 | 60 | 48 | 96 |
curmn | -5 | -10 | -30 | -12 | -240 | -480 |
6.问题来了,为什么这个方法比之前枚举各种可能性的简洁,而且好理解呢?这个方法是后面想到的,自己也想过要找最大值、最小值,但是没定义清楚,比如:我们最后需要的最大最小值是包含当前num[i]的最大最小值,而不是从左往右一直乘的最大最小值。这两者的区别是,前者在每次最大最小值,同时考虑了当前值是否比从左往右迭代过来的值还大或还小,由于每次算最大最小值,都考虑了这个信息,所以可以一直保证得到包含目前num[i]的最大最小值。而这个最大值的最大值即使所求!
7. 之前想的第一种方法,只考虑了现象,试图用代码去模拟这个现象,还没抓住本质,因此深入不够,没有一个抽象的点在里面。而后面的方法,就是求包含num[i]的最大最小值,这就是把一个大的目标分割成小的目标,然后我们一直在这个小目标用功,大的目标也就得到了!本质是问题的分割,由于问题可以分割,所以必然是dp.
class Solution {
public:
int maxProduct(vector<int>& nums) {
//
int n=nums.size();
int curmx=nums[0];
int curmn=nums[0];
int res=nums[0];
for(int i=1;i<nums.size();i++){
if(nums[i]>0){
curmx*=nums[i];
curmn*=nums[i];
}else{
int tmp=curmx;
curmx=curmn*nums[i];
curmn=tmp*nums[i];
}
curmx=nums[i]>curmx?nums[i]:curmx;
curmn=nums[i]<curmn?nums[i]:curmn;
res=max(res,curmx);
}
return res;
}
};