给你一个整数数组 nums
,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
提示:
1 <= nums.length <= 2 * 104
-10 <= nums[i] <= 10
nums
的任何前缀或后缀的乘积都 保证 是一个 32-位 整数
子数组 是数组中连续的 非空 元素序列
思路
子数组要求连续,因此当前位置的最大连续子数组乘积可以依赖上一个位置的最大子数组乘积
即 max(dp[i], dp[i-1]*nums[i])
但是,该题目存在负数的情况,此时可能会出现 负负得正 的结果,但是如果只是存储以当前元素为末尾的数组的最大乘积,可能会遗漏负负得正的结果,因为每次都不会取负数的结果
如样例 [-2,3,-4]
对于 dp[1] = max(3, -2*3)
不会取 -6,因此会错过 -2 * 3 -4 = 28
的结果
因此还需要记录以当前位置为末尾数组的最小数组乘积 dpMin
在计算连续最大乘积时,考虑 dpMin[i-1]*nums[i]
的情况
dp 数组
dpMax[i]
表示以当前下标 i 为末尾的数组 的 最大连续数组乘积
dpMin[i]
表示以当前下标 i 为末尾的数组 的 最小连续数组乘积
如果 dpMin[i] 都为正数,也不会影响结果的推定
初始化
初始化为 nums[i]
即长度为 1 时各自的最大最小乘积
递推公式
最大值考虑 当前值、前一个推导结果的最大值乘以当前元素、前一个推导结果的最小值乘以当前元素
最小值与之对称考虑
// 连续的子数组
dpMax[i] = Math.max(Math.max(dpMax[i], dpMax[i-1]*nums[i]), dpMin[i-1]*nums[i]);
dpMin[i] = Math.min(Math.min(dpMin[i], dpMin[i-1]*nums[i]), dpMax[i-1]*nums[i]);
遍历顺序
从左到右
题解
样例中存在大数量级的样例
[0,10,10,10,10,10,10,10,10,10,-10,10,10,10,10,10,10,10,10,10,0]
java 中可以使用 BigInteger 进行表示
或者直接使用 double 强制转换,针对该样例,不会出现异常
public int maxProduct(int[] nums) {
/*
[0,10,10,10,10,10,10,10,10,10,-10,10,10,10,10,10,10,10,10,10,0]
异常的数量级
*/
double[] dpMax = new double[nums.length]; // 最大值
double[] dpMin = new double[nums.length]; // 最小值,负数
for (int i = 0; i < nums.length; i++) {
dpMax[i] = nums[i];
dpMin[i] = nums[i];
}
// 遍历从第二个元素开始,初始最大值为 dp[0]
double max = dpMax[0];
for (int i = 1; i < nums.length; i++) {
// 连续的子数组
dpMax[i] = Math.max(Math.max(dpMax[i], dpMax[i-1]*nums[i]), dpMin[i-1]*nums[i]);
dpMin[i] = Math.min(Math.min(dpMin[i], dpMin[i-1]*nums[i]), dpMax[i-1]*nums[i]);
if (max < dpMax[i]) {
max = dpMax[i];
}
}
return (int)max;
}