LeetCode 152. 乘积最大子数组
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
解析:
不包含0的情况
1.0 全为正数
这种情况最简单,直接所有结果相乘即可。
2.1 偶数个负数
负负得正,这种情况也直接将结果相乘即可。
2.2 奇数个负数
奇数是2n+1(或者1+2n),这里2n就是2.1中的情况,偶数个负数相乘结果肯定是正数,再把它附近的所有正数连在一起就构成了最大连乘的子区间。现在要解决的问题就是怎么样排除第一个或者最后一个负数,下面的解法比较巧妙。
从左往右乘,从右往左乘,最后两个区间结果的最大值即为所求的最大连乘结果。
包含0的情况
同上面一样,分别从左往右以及从右往左连乘,一旦遇到0,那么下一个数就为它本身,表示区间从0处断开。
为什么呢?你想啊,你之前保留连乘的结果,不管正数还是负数,你都希望后面继续有其他正数或者负数能与之相乘,使结果趋于更大。这时候来了个0,连乘更大的希望就瞬间破灭了,一夜回到解放前。那么从下一个数开始,你是选择继续为0呢,还是重拾希望呢?
综上所述:
所有情况都可以通过从左往右连乘和从右往左连乘进行求解:
全为正数或者偶数个负数,从头乘到尾最大
奇数个负数不含0,从左往右乘到最后一个负数的左面为最大值,之后的乘积都为负数;从右往左乘到最前面一个负数的右边为最大值,再往前乘也都为负数;然后比较这两个最大值,选取最大的那一个
含有0的情况,同上,一旦遇到0,需要断开,也就是后面的乘积要重新开始。
可以看到,以上所有情况都可以进行从左往右和从右往左进行连乘,然后求两种方式的最大值,再选取最大的那一个.
好了,看代码
class Solution:
def maxProduct(self, nums: List[int]) -> int:
nums_reverse = nums[::-1]
for i in range(1, len(nums)):
if nums[i - 1] != 0:
nums[i] *= nums[i - 1]
if nums_reverse[i - 1] != 0:
nums_reverse[i] *= nums_reverse[i - 1]
return max(max(nums), max(nums_reverse))
解法二:
可以观察到,
如果nums[i]<0,那么当前最大值可以由前面的最小值和nums[i]相乘得到,
如果nums[i]>0,那么当前最大值可以由前面的最大值和nums[i]相乘得到,
如果前面为0或者为负数,那么nums[i]可能就是最大值
有点难懂,等吃透了再更新,先放代码.
class Solution:
def maxProduct(self, nums: List[int]) -> int:
ans = nums[0]
pre_max = nums[0]
pre_min = nums[0]
for num in nums[1:]:
cur_max = max(pre_max*num, pre_min*num, num) # 保存最大值
cur_min = min(pre_max*num, pre_min*num, num) # 保存最小值,为下一轮做准备
ans = max(ans, cur_max) # 更新最大值
pre_max = cur_max
pre_min = cur_min
return ans