This commit is contained in:
youngyangyang04
2020-12-22 15:42:02 +08:00
parent 1b906da06e
commit f881e3ec27
9 changed files with 245 additions and 56 deletions

View File

@@ -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