mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2026-02-02 18:39:09 +08:00
Update
This commit is contained in:
@@ -1,62 +1,88 @@
|
||||
# 什么是动态规划
|
||||
|
||||
动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
|
||||
|
||||
所以动态规划中每一个状态一定是由上一个状态推导出来的,**这一点就区分于贪心**
|
||||
所以动态规划中每一个状态一定是由上一个状态推导出来的,**这一点就区分于贪心**,贪心是局部直接选最优的,
|
||||
|
||||
在[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中我举了一个背包问题的例子。
|
||||
|
||||
例如:有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。**每件物品只能用一次**,求解将哪些物品装入背包里物品价值总和最大。
|
||||
|
||||
动态规划中dp[j]是又dp[j-weight[i]]推导出来的。
|
||||
|
||||
但如果是贪心呢,dp[j]每次选一个最大的或者最小的就完事了。
|
||||
|
||||
所以贪心解决不了动态规划的问题,这也是最大的区别。
|
||||
|
||||
很多讲解动态规划的文章都会讲最优子结构啊和重叠子问题啊这些,这些东西都是教科书的上定义,晦涩难懂而且不实用。
|
||||
|
||||
大家只要知道,动规是由前一个状态推导出来的,而贪心是局部直接算最优的,就够用了。
|
||||
|
||||
对于上述提到的背包问题,后序会详细讲解。
|
||||
|
||||
|
||||
# 动态规划的解题步骤
|
||||
|
||||
题目的时候,很多同学会陷入一个误区,就是以为把状态转移公式背下来,照葫芦画瓢改改,就开始写代码,甚至把题目AC之后,都不太清楚dp[i]表示的是什么。
|
||||
|
||||
这就是一种朦胧的状态,然后就把题给过了,遇到稍稍难一点的,可能直接就不会了,然后看题解,然后继续照葫芦画瓢陷入这种恶性循环中。
|
||||
|
||||
关于状态转移公式,
|
||||
状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。
|
||||
|
||||
对于动态规划问题,我将拆解为如下四步曲,这四步都搞清楚了,才能说把动态规划真的掌握了!
|
||||
**对于动态规划问题,我将拆解为如下四步曲,这四步都搞清楚了,才能说把动态规划真的掌握了!**
|
||||
|
||||
* 确定dp数组以及下标的含义
|
||||
* dp数组如何初始化
|
||||
* 确定递推公式
|
||||
* dp数组如何初始化
|
||||
* 确定遍历顺序
|
||||
|
||||
一些同学可能想为什么要先确定递推公式,然后在考虑初始化呢?
|
||||
|
||||
因为一些情况是递推公式决定了dp数组要输入初始化!
|
||||
|
||||
后面的讲解中我都是围绕着这四个点来经行讲解。
|
||||
|
||||
可能刷过动态规划题目的同学可能都知道递推公式的重要性,感觉确定了递推公式这道题目就解出来了。
|
||||
|
||||
其实 确定递推公式 仅仅是解题里的一步而且, dp数组的初始化 以及确定遍历顺序,都非常重要,
|
||||
其实 确定递推公式 仅仅是解题里的一步而且,dp数组以及下标的含义, dp数组的初始化 以及确定遍历顺序,都非常重要。
|
||||
|
||||
**很多同学搞不清楚dp数组应该如何初始化,或者遍历的顺序,以至于记下来公式,但写的程序怎么改都通过不了**。
|
||||
后序的讲解的大家就会发现其重要性。
|
||||
|
||||
# 动态规划如何debug
|
||||
很多同学甚至知道递推公式,但搞不清楚dp数组应该如何初始化,或者正确的遍历顺序,以至于记下来公式,但写的程序怎么改都通过不了。
|
||||
|
||||
# 动态规划应该如何debug
|
||||
|
||||
平时我自己写的时候也经常出问题,**找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!**
|
||||
|
||||
一些同学对于dp的学习是上来就是俯视类型的,总是想一下子全盘接纳,一鼓作气写出代码,如果代码能通过万事大吉,通过不了的话就凭感觉改一改。
|
||||
|
||||
# 背包三讲
|
||||
这是一个很不好的习惯!
|
||||
|
||||
背包九讲其实看起来还是有点费劲的,而且都是伪代码理解起来吃力
|
||||
<img src='../pics/416.分割等和子集1.png' width=600> </img></div>
|
||||
**做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果**。
|
||||
|
||||
然后在写代码,如果代码没通过就打印dp数组,看看是不是和自己推导的哪里不一样。
|
||||
|
||||
如果和自己模拟推导的一样,那么就是自己的递归公式有问题。
|
||||
|
||||
如果和自己模拟推导的不一样,那么就是代码实现细节有问题。
|
||||
|
||||
**这样才是一个完整的思考过程,而不是一旦代码出问题,就毫无头绪的东改改西改改,最后过不了,或者说是稀里糊涂的过了**。
|
||||
|
||||
我来举一个例子:一些同学可能代码通过不了,都会把代码抛到出来问:我这里代码都已经和题解一模一样的,为什么通过不了呢?
|
||||
|
||||
发出这样的问题之前,其实可以自己先思考这三个问题:
|
||||
|
||||
* 这道题目我推导状态转移公式了么?
|
||||
* 我打印dp数组的日志了么?
|
||||
* 打印出来了dp数组和我想的一样么?
|
||||
|
||||
**如果这灵魂三问自己都做到了,基本上这道题目也就解决了**,或者更清晰的知道自己究竟是哪一点不明白,是状态转移不明白,还是实现代码不知道该怎么写,还是不理解遍历dp数组的顺序。
|
||||
|
||||
然后在问问题,目的性就很强了,回答问题的同学也可以快速知道提问者的疑惑了。
|
||||
|
||||
# 动态规划可以解决哪一类问题
|
||||
|
||||
|
||||
|
||||
|
||||
# 完全背包
|
||||
|
||||
有N 种物品和一个容量为V 的背包,每种物品都有无限件可用。放入第i种 物品的耗费的空间是Ci ,得到的价值是Wi 。求解:将哪些物品装入背包,可使 这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
|
||||
|
||||
这个问题非常类似于01背包问题,所不同的是每种物品有无限件
|
||||
|
||||
|
||||
首先想想为什么01背包中要按照v递减的次序来 循环。让v递减是为了保证第i次循环中的状态F [i, v]是由状态F [i − 1, v − Ci]递 推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入 第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果F [i − 1, v − Ci]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加 选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结 果F [i, v − Ci],所以就可以并且必须采用v递增的顺序循环。这就是这个简单的
|
||||
程序为何成立的道理。
|
||||
|
||||
|
||||
值得一提的是,上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。(可能说的就是组合或者排列了)
|
||||
|
||||
# 多重背包
|
||||
|
||||
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费 的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
|
||||
|
||||
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略 微一改即可。
|
||||
|
||||
|
||||
# 总结
|
||||
|
||||
后台回复:背包九讲 就可以获得pdf
|
||||
|
||||
Reference in New Issue
Block a user