力扣152:乘积最大子数组
力扣152:乘积最大子数组
- 题目
- 思路
- 代码
题目
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
思路
这道题我们首先想到的思路应该是动态规划,定义一个一维数组dp每一个位置i都存包含当前位置的最大乘积,这个最大乘积是当前值nums[i]和dp数组的前一个位置乘上当前位置
dp[i-1]*nums[i]的最大值。然后再定义一个整数用来保存整个过程中的最大乘积,用这种方法是可以过一部分的用例的但是会遇到一个很大的问题就是题目里没有说这个数组是一个正数数组也就是说这个数组是有负数的,用我们上面那个方法在遇到负数时肯定就判断最大乘积是当前值了因为乘一个负数肯定会变小嘛,那么问题就出现了在遇到一个负数后我们又遇到了一个正数此时最大乘积就变成了这个正数的值,在后面我们又遇到一个负数这时候的最大乘积就变成了这个负数。这就有问题了,乘一个负数值是会变小的但是乘两个负数值是会变大的啊。所以当数组里有两个负数时最后的子数组应该是会包含到这两个负数的但是实际我们的答案并不会因为我们遇到负数就更新最大乘积是当前负数的值了,这就是我们的问题。想要对这个动态规划做优化的话我们就需要对这个负数情况做处理。
但是现在我不用这种方法了我们想一想其他的思路,在经过上面的分析时发没发现一个情况那就是子数组的两边是不可能都有负数的因为只要乘上这两个负数整个的乘积是肯定会变大的啊,还有就是子数组的任意一侧是不可能有正数的,一样的情况乘上这个正数整个的乘积是会变大的而我们求得就是最大乘积。所以这就导致了我们最后的子数组的两端一定是0或者是数组的起止位置。那么我们是否可以定义两个整数i,j来代表从头到尾的下标以及从尾到头的下标。同时再定义两个整数pre,bck分别存储从头到尾的乘积和从尾到头的乘积。这时候整个数组我们可以分为三种情况
- 数组都是非负数
这种情况又包含数组里有0和没有0,有0时pre和bck就会分别求得0前面的子数组的乘积和和0后面的子数组的乘积和,最终答案肯定就是这两个其中一个。没有0的时候pre和bck的值最后是一样的,子数组就是原数组。 - 数组有奇数个负数
奇数个负数时pre和bck也会各自求得负数前面的子数组的和和负数后面的子数组的和。 - 数组有偶数个负数
偶数个负数等于就是都是正数,也就是直接获得整个数组的乘积和。
注意:第二种和第三种情况都是在数组没有0的情况下,如果有0就参考第一种情况,逻辑是一样的。
在经过上面三种情况的讨论后我们就可以定义一个整数res来当最大乘积也就是答案了,在移动pre和bck时我们也需要判断res的值,那么是从哪几个值里找最大值呢有四个值即res,当nums[i],pre,bck。可能会有疑惑为什么要带上nums[i]呢,我直接举例子大家就知道了当数组为[-2,0,-1]时,最大乘积不就是0吗。所以带上nums[i]是因为子数组有可能只有一个元素。
代码
class Solution {
public:int maxProduct(vector<int>& nums) {int res = INT_MIN;int i = 0;int j = nums.size() - 1;//从前往后的乘积int pre = 1;//从后往前的乘积int bck = 1;while (i < nums.size()) {pre *= nums[i];bck *= nums[j];//比较res,nums[i],pre,bck的值,找到最大值res = max(max(res, nums[i]), max(pre, bck));//遇到零就重新计算if (nums[i] == 0)pre = 1;if (nums[j] == 0)bck = 1;i++;j--;}return res;}
};