动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。
大家知道动规是由前一个状态推导出来的,而贪心是局部直接选最优的,对于刷题来说就够用了。
为了方便区分这两种方法,我们称第一种解法为**「经验解法」,第二种为「技巧解法」**吧。
状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
先确定递推公式再考虑dp数组的初始化的原因:因为一些情况是递推公式决定了dp数组要如何初始化!
后面做题一直围绕这五点来做。
写动规题目,代码出问题很正常。
找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!
- 做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果。
- 然后再写代码,如果代码没通过就打印dp数组,看看是不是和自己预先推导的哪里不一样。
- 如果打印出来和自己预先模拟推导是一样的,那么就是自己的递归公式、初始化或者遍历顺序有问题了。
- 如果和自己预先模拟推导的不一样,那么就是代码实现细节有问题。
- 这样才是一个完整的思考过程,而不是一旦代码出问题,就毫无头绪的东改改西改改,最后过不了,或者说是稀里糊涂的过了。
发出bug的时候,其实可以自己先思考这三个问题:
- 确定dp数组以及下标的含义:
- 确定递推公式:
- dp数组如何初始化:
- 确定遍历顺序:
- 举例推导dp数组:
动态规划,dp,压缩版——2022年8月16日15:37:38 - 斐波那契数 - 力扣(LeetCode)
动态规划,dp,爬楼梯——2022年8月16日16:08:09 - 爬楼梯 - 力扣(LeetCode)
动态规划,dp,爬楼梯——2022年8月16日17:22:26 - 使用最小花费爬楼梯 - 力扣(LeetCode)
1)路径问题(可视化入门dp):
【科学派 DP】一份「路径问题从入门到进阶」的究极指南 … (qq.com)
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓== 个人题解如下 ==↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
动态规划,dp,二维dp——2022年8月16日17:41:25 - 不同路径 - 力扣(LeetCode)
动态规划,dp,二维dp——2022年8月16日18:25:22 - 不同路径 II - 力扣(LeetCode)
dp,路径问题,二维dp,路径储存问题!——2022年8月25日15:25:30 - 最小路径和 - 力扣(LeetCode)
dp,二维dp,一维dp,滚动数组——2022年8月25日16:55:15 - 三角形最小路径和 - 力扣(LeetCode)
dp,二维数组——2022年8月25日17:21:54 - 下降路径最小和 - 力扣(LeetCode)
dp,二维dp,回溯操作理解题意,路径问题——2022年8月25日18:47:35 - 下降路径最小和 II - 力扣(LeetCode)
事实上,任何「记忆化搜索」都能改成「动态规划」。
记忆化搜索的实现方式:利用数组来记录已经计算出来的重叠子问题,这个和动态规划的思想非常相似,没错,记忆化搜索其实用的就是动态规划的思想。更加确切的说,可以用如下公式来表示:
记忆化搜索=深度优先搜索的实现+动态规划的思想
记忆化搜索 —— 搜索 or 动态规划 ? - 知乎 (zhihu.com)
我们需要先有一个「记忆化搜索」解法。所谓的有一个「记忆化搜索」,我们只需要考虑 DFS 函数会如何设计即可,而不需要真正去实现一个「记忆化搜索」。【动态规划/路径问题】强化忽略「状态定义」&「转移方程」来求解 DP 的「技巧解法」
记忆性搜索,转化成动态规划dp——2022年8月26日19:16:03 - 统计所有可行路径 - 力扣(LeetCode)
动态规划dp,从记忆性搜索到dp——2022年8月27日00:56:37 - 出界的路径数 - 力扣(LeetCode)
动态规划dp,二维dp,降维操作——2022年8月27日21:22:38 - 最大得分的路径数目 - 力扣(LeetCode)
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑== 路径问题个人题解如上 ==↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
j怎么就不拆分呢?
j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j 取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
如果定义dp[i - j] * dp[j] 也是默认将一个数强制拆成4份以及4份以上了。
动态规划,dp,特别的状态方程——2022年8月16日19:01:34 - 整数拆分 - 力扣(LeetCode)
“你想一个dp问题一定不能光想,而是要用笔涂涂画画,先考虑只有一个结点,再考虑两个结点,三个结点,就能发现其实三个结点的答案能由前两个结点的答案推出,自然而然就想到dp了”
就是先找规律,你会发现,这个是从左往右,节点依次当根节点。dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]
动态规划,dp,二叉搜索树——2022年8月16日22:13:52 - 不同的二叉搜索树 - 力扣(LeetCode)
你的背包问题bgm:
你的背包-陈奕迅(kugou.com)
背包讲解系列链接:
0-1背包问题_兴趣使然的小小的博客-CSDN博客
完全背包问题_兴趣使然的小小的博客-CSDN博客
题解链接:
背包问题题解汇总_兴趣使然的小小的博客-CSDN博客
(当然这里还可以把空间优化到O(1))
居民家一条线段的排布:动态规划,dp——2022年9月2日20:50:19 - 打家劫舍 - 力扣(LeetCode)
居民家围成了一个圈:动态规划,dp——2022年9月2日21:13:13 - 打家劫舍 II - 力扣(LeetCode)
居民家类似二叉树排列(树形dp):动态规划,树形dp——2022年9月2日22:35:51 - 打家劫舍 III - 力扣(LeetCode)
这些天只能买一次再卖出:贪心,二维dp,一维dp——2022年9月3日18:29:31 - 买卖股票的最佳时机 - 力扣(LeetCode)
这些天能进行多次交易:贪心,二维dp,一维dp——2022年9月3日19:51:52 - 买卖股票的最佳时机 II - 力扣(LeetCode)
这些天能进行最多两次交易:二维dp——2022年9月3日20:26:22 - 买卖股票的最佳时机 III - 力扣(LeetCode)
这些天要进行最多k次交易:二维dp——2022年9月3日21:22:38 - 买卖股票的最佳时机 IV - 力扣(LeetCode)
进行交易后一天会冷冻一天:二维dp——2022年9月4日01:06:17 - 最佳买卖股票时机含冷冻期 - 力扣(LeetCode)
这些天能进行多次交易(要交手续费):二维dp,贪心——2022年9月4日01:29:24 - 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
tips:记住,子序列默认不连续,子数组默认连续
tips:有趣的是下面这三个题,dp[i]都是必须包括当前nums[i]的,中间可能就产出最大值了,所以用一个ans来时刻记录最大值。
所求子序列的元素不需要连续:Java,动态规划,子序列问题(不连续)——2022年9月8日23:36:15 - 最长递增子序列 - 力扣(LeetCode)
所求的子序列元素是连续的:Java,动态规划,子序列问题(连续)——2022年9月8日23:56:39 - 最长连续递增序列 - 力扣(LeetCode)
所求的是子数组,是连续的,两个数组:Java,动态规划,连续子序列问题——2022年9月9日00:41:38 - 最长重复子数组 - 力扣(LeetCode)
所求的是子序列,可以不连续,两个数组:Java,动态规划,不连续子序列问题——2022年9月9日19:30:45 - 最长公共子序列 - 力扣(LeetCode)
所求的是不相交线,其实就是上面这个题的copy:Java,动态规划,不连续子序列问题——2022年9月9日22:19:51 - 不相交的线 - 力扣(LeetCode)
所求是最大子数组之和,连续。Java,动态规划,不连续子序列问题,贪心——2022年9月9日22:42:36 - 最大子数组和 - 力扣(LeetCode)
编辑距离(特别篇):
看看相等的部分长度是不是符合s串:Java,动态规划,不连续子串问题——2022年9月11日22:05:06 - 判断子序列 - 力扣(LeetCode)
待做todo
待做
动态规划是一个很大的领域,这一篇是动态规划的整体概述,讲解了什么是动态规划,动态规划的解题步骤,以及如何debug。
因为 DP 是一个递推的过程,因此如果数据范围是 105~106 的话,可以考虑是不是可以使用一维 DP 来解决;如果数据范围是 102~103的话,可以考虑是不是可以使用二维 DP 来做 。