- 动态规划算法的核心就是记住已经解决过的子问题的解
- 斐波那契数列是一个非常好的题目,既与递归有关,也与DP有关。可以从这个简单问题,思考递归与DP的内在关联。
- 递归往往是从后往前计算
- DP往往是从前往后计算
- 用DP解题,一般要思考三个重要问题。
- DP数组的定义是什么?
- 动态转移方程是什么?
- 如何对DP数组进行初始化?
- 解答了这三个问题,代码基本上呼之欲出。千万不要拘泥于复杂的定义,要重理解而轻概念。
- 动态规划(序列DP)
- 入门问题(理解DP基础概念)
- LeetCode 509、斐波那契数
- 传统法一:如果使用递归,需要调用次数呈指数级增长,非常低效!
- 法二:记忆化搜索/备忘录写法 调用次数呈线性增长,高效
- 初始化一个长度为N + 1的数组memo,如果发现memo[N]为初始化的值,说明当前memo还没有计算,就执行递归,其实是一种记忆法,计算过的就不需要重复计算了
- if self.memo[N] == -1:
- self.memo[N] = self.myFib2(N - 1) + self.myFib2(N - 2)
- 法三:DP解法
- 初始化长度为N + 1的数组memo,遍历2到N,我们只考虑当前i位置的值和什么有关,memo[i] = memo[i-1] + memo[i-2]
- 初始化长度为N + 1的数组memo,遍历2到N,我们只考虑当前i位置的值和什么有关,memo[i] = memo[i-1] + memo[i-2]
- LeetCode 70、爬楼梯(和509题一样)
- LeetCode 509、斐波那契数
- 路径问题
以LC62. 不同路径为种子题的二维路径问题,本质上大同小异,仅需在种子题的加以简单的修改,即可完成多道题目。- 对于路径问题而言,移动方向往往是具有限制条件的,譬如从矩阵的左上方移动到右下方,只能向下和向右移动等等,如果不加这种限制条件,那么较难使用DP算法解题,而应该转而考虑DFS/BFS等搜寻算法来完成。
- LeetCode 62、不同路径(模板题)
- 机器人到达(i,j)位置有两种可能,即从(i-1, j)和从(i, j-1)到达
- 第一步:定义dp数组
- dp[i][j]表示从第0行第0列到达第 i 行 第 j 列时不同路径的数量
- dp[i][j]表示从第0行第0列到达第 i 行 第 j 列时不同路径的数量
- 第二步:确定动态转移方程
- dp[i][j] = dp[i-][j] + dp[i][j-1]
- 第三步:对DP数组进行初始化
- dp[0][j] = 1,dp[i][0] = 1 (因为机器人只能向右或者下走)
- 第一步:定义dp数组
- 机器人到达(i,j)位置有两种可能,即从(i-1, j)和从(i, j-1)到达
- LeetCode 63、不同路径II
- 和62的模板一样,只是遇到了有障碍物的情况
- 一旦出现了障碍,那么后面所有的位置都是到达不了的,都是默认的 0
- 初始化中:遇到障碍,直接break,说明之后的位置都到不了
- 动态转移方程中:如果此时出现了障碍,那么由于无法到达这个位置,因此不用处理,continue即可
- LeetCode 64、最小路径和
- 第一步:定义dp数组
- dp[i][j]表示从第0行第0列到达第 i 行 第 j 列时最小的路径和
- 第二步:确定动态转移方程
- dp[i][j] = dp[i-][j]+当前格子值或dp[i][j-1]+当前格子值
- 比较上述的两种情况哪个最小,最为dp[i][j]的最终结果
- 第三步:对DP数组进行初始化
- dp[0][j] = 1,dp[i][0] = 1 (因为机器人只能向右或者下走)
- 第一步:定义dp数组
- 打家劫舍问题
- LeetCode198、 打家劫舍(模板题)
- 定义子问题:从k个房子中能偷到的最大金额
- 方案一:只偷前k-1间房子,最后一间不偷
- 方案二:偷前k-2间房子,再偷最后一间
- 定义DP数组:其中的元素vi为前i个房间中能偷到的最大金额
- 那么对于每一个vi,我们比较“前i-2的金额(v_i-2)+偷当前房间金额vi”与“前i-1的金额(v_i-1)+不偷当前房间金额”的大小,大的为vi
- 定义子问题:从k个房子中能偷到的最大金额
- LeetCode213、 打家劫舍II
- 相比于198题,要加入一个环的处理:我们自己来把这个环断掉,即在不选择第一个房间的情况下,按照198计算一个结果,在不选择最后一个房间的情况下,计算一个结果,比较这两个结果的最大值即为最终的最大金额。
- 相比于198题,要加入一个环的处理:我们自己来把这个环断掉,即在不选择第一个房间的情况下,按照198计算一个结果,在不选择最后一个房间的情况下,计算一个结果,比较这两个结果的最大值即为最终的最大金额。
- LeetCode198、 打家劫舍(模板题)
- 最长递增子序列(LIS)问题(极其重要,掌握理解,面试高频)
LIS问题是非常经典的序列DP问题,其DP解法不难理解,同时属于高频考题,强烈建议掌握。
LIS问题还存在更优的贪心+二分查找的解法,属于较难想到、较难理解的思路,感兴趣且学有余力的话可以学习一下。通常而言,DP解法已经足够解决10^4这个数据量级的题目了。- LeetCode300. 最长递增子序列 (必懂题)
- DP数组的定义:设置数组 dp,用来存储 nums 中以每个元素结尾的最长递增序列的程度
- 首先将数组 dp 里面的值都初始化为 1 ,1 表示以当前元素结尾的最长递增序列的长度为 1 ,即最长递增序列就是当前元素本身
- 为什么要dp[i] < dp[j] + 1 ?
- 因为dp[j] + 1表示如果dp[i]采纳了当前的选择会更新的值,那么如果更新的这个值大于了现在的dp[i] 的话,就说明比当前的长度长,所以要更新
- LeetCode673. 最长递增子序列的个数
- 和300题的区别在于加入了count[i]: 计算以nums[i]为结尾的LIS的个数
- 方框内的语句怎么理解?就是说如果当前遍历的dp[j]+1(以nums[j]结尾的最长递增子序列长度加上1,即加上nums[i]后),比dp[i]大,说明我们需要更新dp[i],同时因为更新了,所以“该位置的最长递增子序列的个数”是会变成“以nums[j]结尾的最长递增子序列的个数”的。如果是相等关系,则说明不需要更新,所以“该位置的最长递增子序列的个数”会累加上“以nums[j]结尾的最长递增子序列的个数”
- 方框内的语句怎么理解?就是说如果当前遍历的dp[j]+1(以nums[j]结尾的最长递增子序列长度加上1,即加上nums[i]后),比dp[i]大,说明我们需要更新dp[i],同时因为更新了,所以“该位置的最长递增子序列的个数”是会变成“以nums[j]结尾的最长递增子序列的个数”的。如果是相等关系,则说明不需要更新,所以“该位置的最长递增子序列的个数”会累加上“以nums[j]结尾的最长递增子序列的个数”
- 和300题的区别在于加入了count[i]: 计算以nums[i]为结尾的LIS的个数
- LeetCode334. 递增的三元组
- 这里用300题的代码做(判断maxLength是否≥3)是可以的,但是会超时!因为DP对于10^4这个数据量级的题目有用,但是超过这个范围的数据就不行了,需要加入二分查找、贪心等思想
- 贪心思想
- 我们设置最小值、中间值、最大值,当发现最大值(比如4)小于中间值(比如6)的时候,我们就把中间值更新为这个最大值(4)(因为中间值更新为这个较小的值后,我们发现更好地找到正确的最大值,毕竟4要比6小,那么4~6之间的数字就可以当最大值了)
- 我们设置最小值、中间值、最大值,当发现最大值(比如4)小于中间值(比如6)的时候,我们就把中间值更新为这个最大值(4)(因为中间值更新为这个较小的值后,我们发现更好地找到正确的最大值,毕竟4要比6小,那么4~6之间的数字就可以当最大值了)
- LeetCode300. 最长递增子序列 (必懂题)
- 最长公共子序列(LCS)问题(极其重要,掌握理解,面试高频)
- LCS问题是OD考试非常高频的考点,其难点在于dp数组的构建与定义较难想到。其实用一个二维dp数组来表示两个序列之间的关系,这是一种非常常用的技巧,但如果从来没有见过这样的做法是很难想到应该这样完成的。
- LC1143. 最长公共子序列和LC718. 最长重复子数组两道题之间的差别仅仅在于序列是否可以连续地取,在思路和代码上是非常类似的。
- 如果觉得理解上有些困难,那么这类题是可以直接背诵代码的,因为代码不长。
- LeetCode 718. 最长重复子数组(HJ75. 公共子串计算) (必懂题)(连续的取,所以状态转移方程只能为 dp[i][j] = dp[i - 1][j - 1] + 1)
- DP数组的定义:
- dp[i][j]表示A前ⅰ个元素和B前 j 个元素的公共的、长度最长的子数组的长度
- 那么更新dp状态方程的过程为 dp[i][j] = dp[i - 1][j - 1] + 1
- # dp[i][j] 表示 nums1 前 i 个元素和 nums2 前 j 个元素的公共的、长度最长的子数组的长度
- # dp[i - 1][j - 1] 表示 nums1 前 i - 1 个元素和 nums2 前 j - 1 个元素的公共的、长度最长的子数组的长度
- dp[i][j]表示A前ⅰ个元素和B前 j 个元素的公共的、长度最长的子数组的长度
- 整体代码
-
- DP数组的定义:
- LeetCode 1143. 最长公共子序列(可以跳着取,所以还可以为dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]))
- 可以跳着取,所以状态转移方程还可以为dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
- 注意这里不加1,因为第i和j个元素现在是不相同的,也就不能算入dp里
- 【DP】2023B-跳格子(1) (即LeetCode198、 打家劫舍)
- 【DP】2023B-跳格子(2) (即LeetCode213、 打家劫舍II)
- 入门问题(理解DP基础概念)
第39-40天学习笔记——动态规划DP
最新推荐文章于 2024-07-18 13:20:33 发布