categories: [计算机通识,数据结构与算法,leetcode系列]
thumbnail: /images/fe/leetcode.jpg
toc: true
乘积最大子数组(难度:中等)
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
结题思路:
做这道题时如果做过前面leetcode的数列最大字段和,很容易联想到相同的动态规划算法。例如[5, 6, -3, 4, -3],用之前的想法,我们很容易得出状态转移方程:
就是对于当前元素,要么加入到他前面元素的段中,要么自己做一个新段。这个时候很容易得出下面这个dp数组:[5, 30, -3, 4, -3],于是得出答案是30
但是很明显答案是错的,数组所有元素相乘结果5x6x(-3)x4x(-3)才是最大的值。出现这个问题的原因是我们这个办法没考虑数组中负数的情况,而把他们粗暴地一步步相乘对比。
我们可以根据正负性进行分类讨论。
考虑当前位置如果是一个负数的话,那么我们希望以它前一个位置结尾的段的积也是个负数,这样就可以负负得正,并且我们希望这个积尽可能「负得更多」,即尽可能小。如果当前位置是一个正数的话,我们更希望以它前一个位置结尾的某个段的积也是个正数,并且希望它尽可能地大。
综上所述,这道题我们需要维护两个状态数组,一个保存截止到当前元素最大的乘积,另一个保存截止到当前,最小的乘积
状态转移方程如下:
以上面提到的例子为例:
[5, 6, -3, 4, -3]
5 | 6 | -3 | 4 | -3 | |
---|---|---|---|---|---|
Fmax | 5 | 30 | -3 | 4 | 1080 |
Fmin | 5 | 6 | -90 | -360 | -12 |
在Fmax中找到最大值1080就是这道题的答案
代码如下:
var maxProduct = function(nums) {
let len = nums.length
if(len === 0){
return 0
}
let Fmax =[], Fmin = []
Fmax[0] = Fmin[0] = nums[0]
for(let i = 1; i < len; i++){
Fmax[i] = Math.max(Fmax[i - 1] * nums[i], Fmin[i - 1] * nums[i], nums[i])
Fmin[i] = Math.min(Fmax[i - 1] * nums[i], Fmin[i - 1] * nums[i], nums[i])
}
return Math.max.apply(null, Fmax)
};