leetcode-master/problems/周总结/20210121动规周末总结.md

161 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 本周小结!(动态规划系列三)
本周我们正式开始讲解背包问题,也是动规里非常重要的一类问题。
背包问题其实有很多细节,如果了解个大概,然后也能一气呵成把代码写出来,但稍稍变变花样可能会陷入迷茫了。
开始回顾一下本周的内容吧!
## 周一
[动态规划关于01背包问题你该了解这些](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中,我们开始介绍了背包问题。
首先对于背包的所有问题中01背包是最最基础的其他背包也是在01背包的基础上稍作变化。
所以我才花费这么大精力去讲解01背包。
关于其他几种常用的背包,大家看这张图就了然于胸了:
![416.分割等和子集1](https://img-blog.csdnimg.cn/20210117171307407.png)
本文用动规五部曲详细讲解了01背包的二维dp数组的实现方法大家其实可以发现最简单的是推导公式了推导公式估计看一遍就记下来了但难就难在确定初始化和遍历顺序上。
1. 确定dp数组以及下标的含义
dp[i][j] 表示从下标为[0-i]的物品里任意取放进容量为j的背包价值总和最大是多少。
2. 确定递推公式
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
3. dp数组如何初始化
```C++
// 初始化 dp
vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 0));
for (int j = bagWeight; j >= weight[0]; j--) {
dp[0][j] = dp[0][j - weight[0]] + value[0];
}
```
4. 确定遍历顺序
**01背包二维dp数组在遍历顺序上外层遍历物品 ,内层遍历背包容量 和 外层遍历背包容量 ,内层遍历物品 都是可以的!**
但是先遍历物品更好理解。代码如下:
```C++
// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
```
5. 举例推导dp数组
背包最大重量为4。
物品为:
| | 重量 | 价值 |
| --- | --- | --- |
| 物品0 | 1 | 15 |
| 物品1 | 3 | 20 |
| 物品2 | 4 | 30 |
来看一下对应的dp数组的数值如图
![动态规划-背包问题4](https://img-blog.csdnimg.cn/20210118163425129.jpg)
最终结果就是dp[2][4]。
## 周二
[动态规划关于01背包问题你该了解这些滚动数组](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中把01背包的一维dp数组滚动数组实现详细讲解了一遍。
分析一下和二维dp数组有什么区别在初始化和遍历顺序上又有什么差异
最后总结了一道朴实无华的背包面试题。
要求候选人先实现一个纯二维的01背包如果写出来了然后再问为什么两个for循环的嵌套顺序这么写反过来写行不行再讲一讲初始化的逻辑。
然后要求实现一个一维数组的01背包最后再问一维数组的01背包两个for循环的顺序反过来写行不行为什么
这几个问题就可以考察出候选人的算法功底了。
01背包一维数组分析如下
1. 确定dp数组的定义
在一维dp数组中dp[j]表示容量为j的背包所背的物品价值可以最大为dp[j]。
2. 一维dp数组的递推公式
```
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
```
3. 一维dp数组如何初始化
如果物品价值都是大于0的所以dp数组初始化的时候都初始为0就可以了。
4. 一维dp数组遍历顺序
代码如下:
```C++
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
```
5. 举例推导dp数组
一维dp分别用物品0物品1物品2 来遍历背包,最终得到结果如下:
![动态规划-背包问题9](https://img-blog.csdnimg.cn/20210110103614769.png)
## 周三
[动态规划416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)中我们开始用01背包来解决问题。
只有确定了如下四点才能把01背包问题套到本题上来。
* 背包的体积为sum / 2
* 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
* 背包如何正好装满,说明找到了总和为 sum / 2 的子集。
* 背包中每一个元素是不可重复放入。
接下来就是一个完整的01背包问题大家应该可以轻松做出了。
## 周四
[动态规划1049. 最后一块石头的重量 II](https://mp.weixin.qq.com/s/WbwAo3jaUaNJjvhHgq0BGg)这道题目其实和[动态规划416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)是非常像的。
本题其实就是尽量让石头分成重量相同的两堆相撞之后剩下的石头最小这样就化解成01背包问题了。
[动态规划416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)相当于是求背包是否正好装满,而本题是求背包最多能装多少。
这两道题目是对dp[target]的处理方式不同。这也考验的对dp[i]定义的理解。
## 总结
总体来说,本周信息量还是比较大的,特别对于对动态规划还不够了解的同学。
但如果坚持下来把,我在文章中列出的每一个问题,都仔细思考,消化为自己的知识,那么进步一定是飞速的。
有的同学可能看了看背包递推公式,上来就能撸它几道题目,然后背包问题就这么过去了,其实这样是很不牢固的。
就像是我们讲解01背包的时候花了那么大力气才把每一个细节都讲清楚这里其实是基础后面的背包问题怎么变基础比较牢固自然会有自己的一套思考过程。