LeetCode 152
乘积最大子数组
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
解法1:动态规划
解题思路:
用动态规划的思想,dp数组的含义就是从第i个数字到第j个数字的乘积和
有以下转移方程
dp[i][j] = dp[i][j-1]*nums[j];
代码如下:
class Solution {
public int maxProduct(int[] nums) {
if(nums.length==1)
return nums[0];
int[][] dp = new int[nums.length][nums.length];
int res = Integer.MIN_VALUE;
for(int i=0; i<dp.length; i++) //初始化
{
dp[i][i] = nums[i];
res=Math.max(dp[i][i],res);
}
for(int i=0; i<dp.length; i++)
{
for(int j=i+1; j<dp[0].length; j++)
{
dp[i][j]=dp[i][j-1]*nums[j];
res=Math.max(res,dp[i][j]);
}
}
return res;
}
}
时间复杂度为O(N^2)
空间复杂度为O(N^2)
该解法超内存限制了
解法2:三层for循环
解题思路:
直接用三层for循环计算每一个子数组的乘积和
class Solution {
public int maxProduct(int[] nums) {
if(nums.length==1)
return nums[0];
int res = Integer.MIN_VALUE;
int num;
for(int i=0; i<nums.length; i++) //以i为结尾的子数组
{
for(int j=0; j<=i; j++) //从第0个开始
{
num=1;
for(int k=j; k<=i; k++) //计算从第j到第i的子数组乘积和
num*=nums[k];
res = Math.max(num,res);
}
}
return res;
}
}
时间复杂度为O(N^3)
空间复杂度为O(1)
超时了
解法3:优化后的动态规划
解题思路:
我们还是用动态规划的思想,但是dp数组的含义是,以i为结尾的子数组乘积和
初始化为
for(int i=start; i<=end; i++)
{
num*=nums[i];
dp[i-start]=num;
}
因此我们有这样一个动态转移方程,dp[i]/dp[j]就是表示了从j到i的子数组,如
nums = [2,3,-2,4]
dp = [2,6,-12,-48]
因此-48/2
就表示了3,-2,4
这一子数组
-48/6
就表示了-2,4
这一子数组
-48/-12
就表示了4
这一子数组
因此我们就可以另dp[3] = 上面这一系列子数组的最大值
更新后的值为
dp = [2,6,-12,4]
注意,我们要从后往前更新,因为后面的值不会影响到前面的值
int tmp = dp[i]
tmp = Math.max(tmp, dp[i]/dp[j]) // 0=<j<i
dp[i] = tmp;
还有注意出现0的情况,如果出现0,那么后面的乘积和就都为0了,所以我们把出现0的两边分成两个数组
代码如下:
class Solution {
public int maxProduct(int[] nums) {
if(nums.length==1)
return nums[0];
int res = Integer.MIN_VALUE;
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0); //起始地址
for(int i=0; i<nums.length; i++)
{
if(nums[i]==0)
{
if(i==0) //如果第一个就是0,那么将其除掉
{
list.remove(0);
list.add(1);
}
else //加上0的两边的下标
{
list.add(i-1);
list.add(i+1);
}
}
res = Math.max(res,nums[i]);
}
list.add(nums.length-1); //最后面的地址
for(int i=0; i<list.size(); i+=2) //因为起始和终止地址是成对出现的
{
int start = list.get(i);
int end = list.get(i+1);
res = Math.max(res,maxProduct(nums,start,end));
}
return res;
}
public int maxProduct(int[] nums, int start, int end) //动态规划计算最大乘积和
{
int[] dp = new int[end-start+1];
int res = Integer.MIN_VALUE;
int num=1;
for(int i=start; i<=end; i++) //初始化
{
num*=nums[i];
dp[i-start]=num;
}
for(int i=dp.length-1; i>=0; i--) //从后面往前更新
{
int tmp=dp[i];
for(int j=0; j<i; j++)
{
tmp = Math.max(tmp, dp[i]/dp[j]);
}
dp[i] = tmp;
res = Math.max(res,dp[i]);
}
return res;
}
}
时间复杂度为O(N^2)
空间复杂度为O(N)
解法4:交换最大最小
解题思路:
- 遍历数组时计算当前最大值,不断更新
- 令imax为当前最大值,则当前最大值为 imax = max(imax * nums[i], nums[i])
- 由于存在负数,那么会导致最大的变最小的,最小的变最大的。因此还需要维护当前最小值imin,imin = min(imin * nums[i], nums[i])
- 当负数出现时则imax与imin进行交换再进行下一步计算
代码如下:
class Solution {
public int maxProduct(int[] nums) {
int max = Integer.MIN_VALUE, imax = 1, imin = 1;
for(int i=0; i<nums.length; i++){
if(nums[i] < 0){
int tmp = imax;
imax = imin;
imin = tmp;
}
imax = Math.max(imax*nums[i], nums[i]);
imin = Math.min(imin*nums[i], nums[i]);
max = Math.max(max, imax);
}
return max;
}
}
时间复杂度为O(N)
空间复杂度为O(1)
解法来源
作者:guanpengchn
链接:https://leetcode-cn.com/problems/maximum-product-subarray/solution/hua-jie-suan-fa-152-cheng-ji-zui-da-zi-xu-lie-by-g/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。