「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。
题目
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
子数组 是数组的连续子序列。
示例1
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例2
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
思路
这道题和 最大子数组和 传送门 ☞
很像,但是又有很大不同;
按照上一篇的思路一,可以得出下面时间复杂度为 O(n^2)
的解法:
/**
* @param {number[]} nums
* @return {number}
*/
var maxProduct = function(nums) {
const n = nums.length;
if(n == 1) return nums[0];
let max = Number.MIN_SAFE_INTEGER;
for(let i = 0; i < n; i++) {
let tempAns = 1;
for(let j = i; j < n; j++) {
// 计算当前连续子数组的乘积
tempAns *= nums[j];
max = Math.max(max, tempAns);
}
}
return max;
};
按照思路二,计算以 nums[i]
为结尾的子数组最大乘积,存储为 dp[i]
;按照道理,dp[i] = Math.max(nums[i]* dp[i - 1], nums[i])
;但是考虑下面的例子:
[5, 6, −3, 4, −3]
dp
应该为 [5, 30, −3, 4, −3]
,然后就会得到最大乘积为 30
,但是很明显,最后答案应该为所有数字的乘积;问题出在 负负得正
没有考虑到。如果 nums[i]
为正数,当然希望 dp[i - 1]
也为正数,那么乘积就会变大;反之亦然,当 nums[i]
为负数时,希望 dp[i - 1]
为负数,且越小越好;
所以可以再维护一个数组 mindp
,用于维护以 nums[i]
为结尾的最小乘积,那么此时 dp[i]
应该为三个数取最大值,mindp[i]
应该为三个数取最小值,这三个数分别为 nums[i], nums[i] * dp[i - 1], nums[i] * mindp[i - 1]
解法如下
/**
* @param {number[]} nums
* @return {number}
*/
var maxProduct = function(nums) {
const n = nums.length;
if(n == 1) return nums[0];
let max = nums[0];
const dp = new Array(n).fill(0);
const mindp = new Array(n).fill(0);
dp[0] = mindp[0] = nums[0]
for(let i = 1; i < n; i++) {
let temp1 = nums[i] * mindp[i - 1];
let temp2 = nums[i] * dp[i - 1];
mindp[i] = Math.min(temp1, temp2, nums[i]);
dp[i] = Math.max(temp1, temp2, nums[i]);
max = Math.max(max, dp[i]);
}
return max;
};
思考: 仔细看for循环里面的计算,可以发现 dp[i]、mindp[i]
只和上一步的 dp[i - 1]、mindp[i - 1]
有关,dp[i - 2]、mindp[i - 2] ...
都不会再次使用到;那么其实没有必要使用数组去存储,定义两个变量就好了,这样可以减少内存空间的使用;
优化内存空间
/**
* @param {number[]} nums
* @return {number}
*/
var maxProduct = function(nums) {
const n = nums.length;
if(n == 1) return nums[0];
let max = nums[0];
let prevMin = nums[0];
let prevMax = nums[0];
for(let i = 1; i < n; i++) {
let temp1 = nums[i] * prevMin;
let temp2 = nums[i] * prevMax;
prevMin = Math.min(temp1, temp2, nums[i]);
prevMax = Math.max(temp1, temp2, nums[i]);
max = Math.max(max, prevMax);
}
return max;
};
小结
对于这种,后一个结果可以由前一个结果,经过简单计算得出的题目,一般可以用 动态规划
的思路去处理;在分析递推公式时,需要仔细的分情况讨论,才能得出正确的结果。
LeetCode 👉 HOT 100 👉 乘积最大子数组 - 中等题 ✅
合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍
如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄