前言
最近又把背包系列问题温习了一遍,打算写篇文章将常见的几类背包问题进行总结,提取背包问题核心套路,在自己理解的同时,用通俗的语言解释,让看完本文的人能够快速上手解决一系列的背包问题。
内容包含如下:
- 从基础开始:0-1背包问题
- 再进一步:完全背包
- 顺手拈来:多重背包
- 变形:其他背包问题
限于篇幅问题,拟将该系列拆成两篇,第一篇包含上面三个部分,实际上是对三种基础类型有一定掌握,第二篇,主要讲解基于三种类型衍生而来的背包问题和实际(面试)经常遇到的。
0-1背包
要讲背包问题,都得从0-1背包这个最基础问题开始,基本上能够熟练掌握0-1背包,对付其他的背包问题也问题不大。
问题描述
有 N N N件物品和一个容量为 W W W的背包,第 i i i件物品消耗的容量为 w i w_i wi,价值为 W i W_i Wi,求解放入哪些物品可以使得背包中总价值最大。
上述问题还可以用一个形象的例子描述,就是:
一个小偷,进入主人家偷东西,能偷的东西非常多(如电视机,金表,洋娃娃),但是你的包裹容量有限,如何才能不虚此行?
问题解析
问题了解了,产生问题的根本原因在于能力有限(容量),性价比不一(物品)。其实很容易想到暴力的解法,每个物品都有放和不放两种选择,那么把所有可能的选择列出来,选择价值最大的就可以了。
但是这样计算成本很高 O ( 2 n ) O(2^n) O(2n),因此采用动态规划的方式。
动态规划的方法实际上也是考虑了放与不放两种情况,但它与暴力不同的一点是,他会利用到之前的结果,而不是将每个物品独立来看。
- 明确状态与选择
这里需要引入动态规划中的两个概念:状态和选择,所有动态规划问题都绕不开这两个核心,状态表示了动态规划中会变化的内容,选择则是我们每一步的岔路口,不同的选择会引发不同的状态。
在背包问题中,我们将整个过程从1-N循序渐进,可以发现过程中会变化的东西有两个:背包的容量(放入后减小),可选择的物品(选择过后从选择列表去除),这就是状态。而选择就很容易了,我们只有放与不放两种选择,每一次选择都会引发状态的变化(容量减小或可选择物品减少)。
- 定义dp数组
当明确这两点后,再看我们的需求:给定容量下的最大价值。它主要受限于状态:
- 如果可选择的物品更多(性价比更高),那么最大价值会更高
- 如果背包容量更大,那么最大价值会更高
因此,我们可以定义一个动态数组 d p [ i ] [ w ] dp[i][w] dp[i][w],它表示,在前 i i i个可选择物品中,容量为 w w w时,能得到的最大价值。根据这个定义,我们可以知道,最终要求的就是 d p [ n ] [ W ] dp[n][W] dp[n][W]
解答一下为什么这么定义: 动态规划问题能够加速计算的核心在于有一个动态数组存储了计算结果,避免了不必要的重复计算,我们在定义这个数组的时候,要向我们的目标靠拢,同时需要考虑所有状态的限制。
- 确定状态转移
前面我们依据状态定义了dp数组,接下来要根据选择去更新dp数组。选择无非放与不放(针对第i个物品),将这一点结合到dp数组上体现就是:
- 不放:即不将第i个物品放入背包,那么 d p [ i ] [ w ] dp[i][w] dp[i][w]就和 d p [ i − 1 ] [ w ] dp[i-1][w] dp[i−1][w]一样,相当于没有变化
- 放:当放入物品时,总容量减少第i个物品的容量,总价值提升第i个物品的价值,有 d p