【Leetcode152】乘积最大子数组(动态规划)
文章目录
- 一、题目
- 二、思路
- 三、代码
一、题目
二、思路
(0)读懂题意:题目的“连续”是指位置的连续,而不是说数字的连续,这是个大坑。
(1)确定状态:定义两个状态来记录当前子数组的最大乘积、最小乘积。因为在处理负数时,最小乘积乘以负数可能变为最大乘积。dp_max[i]
表示以nums[i]
结尾的子数组的最大乘积、dp_min[i]
表示以nums[i]
结尾的子数组的最小乘积。
(2)状态转移方程:对于每个元素nums[i]
,我们的dp_max[i]
和dp_min[i]
可以从这三个数中确定:
- 只包含当前元素
nums[i]
。 - 当前元素与之前的最大乘积子数组乘积,即
dp_max[i-1] * nums[i]
。 - 当前元素与之前的最小乘积子数组乘积,即
dp_min[i-1] * nums[i]
。
即状态转移方程可表示为:
dp_max[i] = max(nums[i], dp_max[i-1] * nums[i], dp_min[i-1] * nums[i])
dp_min[i] = min(nums[i], dp_max[i-1] * nums[i], dp_min[i-1] * nums[i])
(3)初始状态+边界条件:以第一个元素结尾的子数组最大乘积就是它本身、以第一个元素结尾的子数组最小乘积就是它本身、初始乘积最大结果为第一个元素。
(4)遍历顺序:从左到右遍历数组。
三、代码
(方法一)按照思路的代码如下,时间复杂度为O(n),空间复杂度为O(n)。
class Solution(object):
def maxProduct(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if len(nums) == 0:
return 0
if len(nums) == 1:
return nums[0]
# 初始化数组
n = len(nums)
dp_max = [0] * n
dp_min = [0] * n
# 初始状态
dp_max[0] = nums[0]
dp_min[0] = nums[0]
cheng_ans = nums[0]
# 从第二个元素开始遍历
for i in range(1, n):
num = nums[i]
dp_max[i] = max(num, dp_max[i-1]*num, dp_min[i-1]*num)
dp_min[i] = min(num, dp_max[i-1]*num, dp_min[i-1]*num)
cheng_ans = max(cheng_ans, dp_max[i])
return cheng_ans
(方法二)为了优化空间复杂度,发现每次当前只利用前一次状态,所以dp_max
和dp_min
没必要单独用两个数组记录所有的状态。但注意在计算状态转移方程时,分别计算dp_max和dp_min都会用到上一次的dp_max和dp_min,这为了用错dp_mxn,可以直接对num
确保是正数后,交换dp_max和dp_min的位置,减少max和min函数的入参个数。
class Solution(object):
def maxProduct(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if len(nums) == 0:
return 0
if len(nums) == 1:
return nums[0]
# 初始化数组
n = len(nums)
# 初始状态
dp_max = nums[0]
dp_min = nums[0]
cheng_ans = nums[0]
# 从第二个元素开始遍历
for i in range(1, n):
num = nums[i]
if num < 0:
dp_max, dp_min = dp_min, dp_max
dp_max = max(num, dp_max*num)
dp_min = min(num, dp_min*num)
cheng_ans = max(cheng_ans, dp_max)
return cheng_ans