视频链接:https://www.bilibili.com/video/BV1GW411Q77S?p=9
动态规划原理(以爬楼梯为例):
1. 原问题和子问题:
原问题:求第n阶台阶的所有走法数量
子问题:拆分为求第1,2,...,n - 1阶的走法数量
2. 状态:到每一阶台阶的走法
3. 边界:到第一阶走法为1种,到第二阶走法为2种
4. 状态转移方程:dp[n] = dp[n - 1] + dp[n - 2],1.要不是从上一阶走一步上来,2.要不是从上两阶走两步上来(不存在分开走两步,因为这种情况已经考虑在1种类)
例题1:爬楼梯
(1)利用递归进行暴力回溯(超时)
class Solution: def climbStairs(self, n: int) -> int: def test(n): if n == 1 or n == 2: #只剩两个台阶时,要不一次走两个,要不一个一个走,共2种走法 return n return test(n - 1) + test(n - 2) #每次都有两种走法 return test(n)(2)动态规划
状态方程:
dp[0] = 1; dp[1] = 2; ... ; dp[n] = dp[n - 1] + dp[n - 2]
class Solution: def climbStairs(self, n: int) -> int: res = [1, 2] i = 2 while i < n: res.append(res[i - 1] + res[i - 2]) i += 1 return res[n - 1]其实动态规划和回溯法思路类似,只是思考的方向相反
暴力回溯法是从后向前推,把未知的部分当做一个整体,需要写出口以及递归的代码
动态规划是从前向后推,把以求得的部分当做一个整体,需要写入口以及递推的代码
例题2:打家劫舍
class Solution: def rob(self, nums: List[int]) -> int: if len(nums) == 0: return 0 if len(nums) == 1: return nums[0] nums[1] = max(nums[0], nums[1]) for i in range(2, len(nums)): nums[i] = max(nums[i - 1], nums[i] + nums[i - 2]) return nums[len(nums) - 1]状态方程有两种情况: 1. 取当前位置n的值,再加上n - 2之前的最大财宝数
2. 不取当前位置n的值,直接取n - 1位置时的最大财宝数
边界为:
1. 第一个位置为当前值
2. 第二个位置为第一个位置与第二个位置中的最大值
例题3:最大字段和
leetcode 53. 最大子序和 https://leetcode-cn.com/problems/maximum-subarray/
状态转移方程主要考虑的是dp[i]与前面状态的关系(如何由前面的状态得到),如dp[i - 1]或者dp[i - 2]的关系甚至dp[i - n],找到关系就好做了,思维要放开
class Solution: def maxSubArray(self, nums: List[int]) -> int: for i in range(1, len(nums)): nums[i] = max(nums[i] + nums[i - 1], nums[i]) return max(nums)
例题4:找零钱
首先dp[i]代表什么不要搞错了,要解决的问题是可以组成的金额能否符合题目给出的金额。所以这里dp[i]存储的是组成目标金额的钞票张数,dp[i]可由i之前的数组成员表示。
class Solution: def coinChange(self, coins: List[int], amount: int) -> int: porp = [0] * (amount + 1) for i in range(1, amount + 1): cur_min = float('inf') for j in coins: if i - j >= 0: cur_min = min(cur_min, porp[i - j]) porp[i] = cur_min + 1 if porp[amount] == float('inf'): return -1 return porp[amount]cur_min = float('inf')最小值初始化,很重要!!
cur_max = float('-inf')
