目录
刷掉了《剑指offer》的天梯后,感觉自己对动态规划,回溯,枚举等类型的问题还感觉十分生疏,就打算把这些类型挑出来个个击破。
背包问题是动态规划的入门问题之一,于是我找到了师兄之前推荐给我的《背包九讲》,就着 Lintcode 的 backpack 天梯,学习了一下这个方面的问题。
简单 01 背包
有一个大小为 m 的背包,有 N 个物品, 每个物品的重量为 A_i,最多能装多满?(Lintcode-92)
这就是最简单的 01 背包问题,01 指得是给的物品只有两种状态,放进包里,或者没放进包里。
如果不知道动态规划,一般脑子里会浮现出贪心的思想,先放最大,然后慢慢填满。如果有测试集,很快就会发现这个思想并不是每步最优,最终导致全局也不是最优解。
如果贪心不能用,那么就枚举吧,一个 N 个物品,每次放一个,把所有情况都存下来,但感觉存储每步的结果好像很麻烦,但是没关系,我们先用一个测试集感受一下使用枚举寻找最优解的过程,在这个过程中来寻找存储的方法。
假如背包大小为11,有 4 个物品,大小分别为 [2, 3, 5, 7]。
- 最初始,背包里没有物品,记为 {0, {}},表示背包重量为 0,后面花括号中为已经装入的物品列表
- 第一次,放入 2,记为 {2, {2}}
- 第三次,放入 3,从背包为 0 时放入,得到 {3, {3}},从背包重量为 2 时放入,得到 {5, {2, 3}}
- 第三次,放入 5,从背包为 0 时放入,可以得到 {5, {5}}, 我们已经有 {5, {2, 3}},但这个问题中我们不记录装的内容,随便保留一个就好了,选择保留新的 {5, {5}}。从背包为 2,3,5 时放入,分别得到 {7, {2, 5}}, {8, {3, 5}}, {10, {2, 3, 5}}
- 第四次,放入 7,可以得到 {7, 0}, {9, {2, 7}}, {10, {3, 7}}, {12, {5, 7}}, 不对,背包大小为 11,这个结果不用记,背包重量为 7,8 和 10 时更加装不下,都不用记录
列一下每一步的表:
1. {0, {0}}
2. {0, {0}}, {2, {2}}
3. {0, {0}}, {2, {2}}, {3, {3}}, {5, {2, 3}}
4. {0, {0}}, {2, {2}}, {3, {3}}, {5, {5}}, {7, {2, 5}}, {8, {5, 3}}, {10, {2, 3, 5}}
5. {0, {0}}, {2, {2}}, {3, {3}}, {5, {5}}, {7, {7}}, {8, {5, 3}}, {9, {2, 7}}, {10, {3, 7}}
我们可以得出最多装入的重量为 10, 那么问题来了,会出现枚举重复吗?这个枚举覆盖到所有情况了吗?我们需要多大的存储空间来完成枚举?
我们每一轮放入的都是不同的物体,不会重复。每一轮都尝试过把目前的物体放入所有上一轮的结果,从列表的过程中也可以看出,肯定覆盖全面了。存储空间的话,只要记录下上面的表就可以了,由于背包内部不用记录,超出背包容量的情况不需要记录,所以只要用 物品个数 * 背包容量 大小的二维数组来存储就好了。
我们来画出这个二维数组:
轮数 i | 物品重量 A_i | 背包重量 j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | - | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 2 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 3 | 0 | 0 | 2 | 3 | 0 | 5 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 5 | 0 | 0 | 2 | 3 | 0 | 5 | 0 |