动态规划通用解法总结

背景:leetcode刷题遇到动态规划的题目,做不出来时看别人的code,也可以理解,但还是没有找到create solution的技巧,单纯的comprehend and remeber,直到遇到了下面这篇题解,终于形成了自己的动态规划通用解题方法,拿所有easy难度的题目试了下,结果横扫

https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems

在动态规划题目中,变形数量最多的差不多就是股票问题了,上述解法也围绕股票问题展开

根据上述帖子,我总结了动态规划的解题方法如下

Question1:如何识别题目适用于动态规划?

某一过程包含多种状态(情况),后一种状态的生成依赖于前面的情况

Quetion2:如何写动态规划?

1、找出问题中的状态(state)和选择(choice)

2、用选择去表达状态之间的转移

like below

for state1 in state1_list:
    for state2 in state2_list:
        for state3 in state3_list:
            dp[state1][state2][state3] = choose(choice1, choice2, choice3)

其中所有的choice都用前面已计算出的状态来表示

Question3:如何优化动态规划?

在上述解题方案中,经常会有不必要的空间复杂度消耗,我们可以发现并通过适用常数个变量的方式替代适用数组或者矩阵。

Example:

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution:
    """步骤一:判断是否为动态规划题目;很明显,下一家的偷法依赖于前面的偷法,不同的偷法导致不同的偷盗总额,属于动态规划"""
    def rob(self, nums: List[int]) -> int:
        if not nums or len(nums)==0:
            return 0
        #步骤二:找到状态和选择;这里状态为当前偷到了第几家,用i表示,选择为偷或者不偷
        dp = {}
        #初始化开始情况,当偷第-1家时,可以理解为偷得金额为0
        dp[-1] = 0
        dp[0] = nums[0]
        #遍历的所有情况
        for i in range(1,len(nums)):
            #这里的选择方法为取最大值,不同的选择分别代表偷与不偷
            dp[i] = max(dp[i-2]+nums[i],dp[i-1])
        return dp[len(nums)-1]   

步骤三:优化空间复杂度;上述解法用到了长为N的辅助列表,空间复杂度为O(n)。优化思路为用几个变量替代辅助列表

可以看到当前循环的dp[i],

依赖于当前循环dp[i-1]与dp[i-2],

即依赖于上一循环的dp[i]与dp[i-1],

所以我们用2个变量即可解决该问题,分别cur与pre即可

代码可优化为

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums or len(nums)==0:
            return 0
        pre = 0
        cur = nums[0]
        for i in range(1,len(nums)):
            tmp = cur
            cur = max(pre+nums[i],cur)
            pre = tmp
        return cur

另外,讲一下python实现动态规划过程中需要注意的一点,

与java不同,java可以直接int[][]初始化多重数组并赋予默认值,然后直接用索引取访问对应元素即可,但python的list结构不会初始化大小,需要我们自己初始化,否则直接使用索引会索引越界

#初始化长度为10的一维数组并赋予默认值0
dp = [0]*10

#初始化10*3的二维数组并赋予默认值0
dp = [[0]*10 for _ in range(10)]

但问题是,某些情况下需要我们去思考默认值赋予什么才比较合适,这样会造成困扰,我们更希望不用去思考初始化为何值(keep None即可),如下

#初始化字典(哈希表)代替一维数组(list)
dp = {}

#初始化10个字典并放到一维数组中
dp = [{}  for _ in range(10)]

这样就解决了不给出默认值用于内存分配导致的角标越界问题

归纳出上述套路之后,leetcode遇到动态规划的题目再也不会毫无头绪啦~~~

原文地址:https://www.cnblogs.com/tianyadream/p/12445997.html