【DP】乘积最大子数组
思路和算法
如果我们用 fmax(i)
来表示以第 i
个元素结尾的乘积最大子数组的乘积,a
表示输入参数 nums
,那么根据「53. 最大子序和」的经验,我们很容易推导出这样的状态转移方程:
fmax(i) = max{f(i-1)×a[i], a[i]}
它表示以第 i
个元素结尾的乘积最大子数组的乘积可以考虑 a[i]
加入前面的 fmax(i-1)
对应的一段,或者单独成为一段,这里两种情况下取最大值。求出所有的 fmax(i)
之后选取最大的一个作为答案。
分析错误与修正
这样做本来看似没有问题,但是如果 a = {5, 6, -3, 4, -3}
,按照前面的算法可以得到答案为 30,即前两个数的乘积。实际上,更大的乘积是包含全体数字的乘积。问题出在哪里呢?最后一个 -3
所对应的 fmax
的值既不是 -3
,也不是 4×(-3)
,而是 5×6×(-3)×4×(-3)
。这说明当前位置的最优解未必是由前一个位置的最优解转移得到的。
正负性的分类讨论
考虑当前位置如果是一个负数的话,我们希望以它前一个位置结尾的某个段的积也是个负数,这样就可以负负得正,并且我们希望这个积尽可能「负得更多」,即尽可能小。如果当前位置是一个正数的话,我们更希望以它前一个位置结尾的某个段的积也是个正数,并且希望它尽可能地大。因此,我们维护一个 fmin(i)
,它表示以第 i
个元素结尾的乘积最小子数组的乘积,从而得到以下的动态规划转移方程:
fmax(i) = max{fmax(i-1)×a[i], fmin(i-1)×a[i], a[i]}
fmin(i) = min{fmax(i-1)×a[i], fmin(i-1)×a[i], a[i]}
这样,第 i
个元素结尾的乘积最大或最小子数组的乘积可以通过加入第 i-1
个元素结尾的乘积最大或最小的子数组的乘积中,二者加上 a[i]
,三者取大或小,决定第 i
个元素结尾的乘积最大或最小子数组的乘积。
来源:力扣(LeetCode)
class Solution:
def maxProduct(self, nums):
if not nums:
return 0
maxF = nums[:]
minF = nums[:]
for i in range(1, len(nums)):
maxF[i] = max(maxF[i - 1] * nums[i], nums[i], minF[i - 1] * nums[i])
minF[i] = min(minF[i - 1] * nums[i], nums[i], maxF[i - 1] * nums[i])
return max(maxF)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了