题目
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-product-subarray
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解区
这是一道标签为动态规划的题目
从这个角度考虑的话,我们可以定义数组
dp[i][2];
其中dp[i][0]表示从 0 到位置 i 的最大乘积和
dp[i][1] 表示从 0 到位置 i 的最小乘积和
需要保留最小值的原因是可能存在负值乘负值的情况
代码如下
public class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
int[][] dp = new int[n][2];
dp[0][0] = nums[0];
dp[0][1] = nums[0];
int ans = nums[0];
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(nums[i],Math.max(nums[i] * dp[i - 1][0] , nums[i]*dp[i-1][1]));
dp[i][1] = Math.min(nums[i],Math.min(nums[i] * dp[i - 1][0] , nums[i]*dp[i-1][1]));
ans = Math.max(ans , dp[i][0]);
}
return ans;
}
}
那么问题来了,不难发现对于每一个dp[i] 我们其实每次只需要 dp[i-1] 即可,
所以此处并不需要定义数组,可以将其简化到使用几个变量
public class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
int premax = nums[0];
int premin = nums[0];
int curmax;
int curmin;
int ans = nums[0];
for (int i = 1; i < n; i++) {
curmax = Math.max(nums[i] , Math.max(premax*nums[i] , premin*nums[i]));
curmin = Math.min(nums[i] , Math.min(premax*nums[i] , premin*nums[i]));
ans = Math.max(ans , curmax);
premax = curmax;
premin = curmin;
}
return ans;
}
}
这是两种较为优质的解法,但是我们还可以仔细地探究这道题目,求最大子数组乘积 和求最大子数组和存在一定的相似性,所以我们也可以尝试使用分治的方法来解决这道题
class Solution {
public int maxProduct(int[] nums) {
return maxSub(nums,0,nums.length-1);
}
public int maxSub(int[]nums,int left,int right ){
// maxrightbordermultipy , minrightbordermultipy,从中间到右边边的最大和最小乘积
int maxrightbordermultipy = Integer.MIN_VALUE,minrightbodermultipy = Integer.MAX_VALUE;
// maxleftbordermultipy , minleftbordermultipy,从中间到左边的最大和最小乘积
int minleftbordermultipy = Integer.MAX_VALUE, maxleftbordermultipy = Integer.MIN_VALUE;
// leftmaxmultipy -> 左边子数组的最大乘积; rightmaxmultipy -> 右边子数组的最大乘积;
int leftmaxmultipy = Integer.MIN_VALUE,rightmaxmultipy = Integer.MIN_VALUE;
//使用leftbordermultipy , rightbodermultipy 来记录向左,右乘积时的值
int leftbordermultipy = 1,rightbordermultipy = 1;
//记录跨越中间部分子数组的最大值
int midmaxmultipy = 0;
int mid = left+(right-left)/2;
if(left==right){
return nums[left];
}else if(left+1 == right){
return Math.max(nums[left] ,Math.max(nums[right], nums[left]*nums[right]));
}
leftmaxmultipy = maxSub(nums,left,mid);
rightmaxmultipy = maxSub(nums,mid,right);
for(int i = mid;i >= left ;i--){
leftbordermultipy *= nums[i];
if(leftbordermultipy > maxleftbordermultipy)
maxleftbordermultipy = leftbordermultipy;
if(leftbordermultipy < minleftbordermultipy)
minleftbordermultipy = leftbordermultipy;
if(leftbordermultipy == 0)
break;
}
for(int i = mid+1 ;i <= right;i++){
rightbordermultipy *= nums[i];
if(rightbordermultipy > maxrightbordermultipy)
maxrightbordermultipy = rightbordermultipy;
if(rightbordermultipy < minrightbodermultipy)
minrightbodermultipy = rightbordermultipy;
if(rightbordermultipy == 0)
break;
}
//取得中间部分子数组的最大乘积
midmaxmultipy = Math.max(maxleftbordermultipy,//左大
Math.max(maxrightbordermultipy, //右大
Math.max(leftbordermultipy*rightbordermultipy,//左边界*右边界
Math.max(minleftbordermultipy*minrightbodermultipy,//左小*右大
Math.max(minleftbordermultipy*maxrightbordermultipy,//左小*右小
Math.max( maxleftbordermultipy*minrightbodermultipy, // 左大*右小
maxleftbordermultipy*maxrightbordermultipy //左大*右大
)
)
)
)
)
);
return Math.max(leftmaxmultipy,Math.max(rightmaxmultipy,midmaxmultipy));
}
}