1 0-1背包
题目:有N件物品和一个容量为V的背包。第i建物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大(每件物品只能被使用一次)
根据题目,可以确定使用动态规划思路进行求解,定义一个数组$dp$,$dp[i][j]$表示有$i$件物品,背包容量是$j$所能装入的物品的最大价值。
则有状态转移方程如下:$dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i])$
其中,每次选取都有两种选择:
- 装入背包(背包能放得下该物品):则问题就转化成前$i-1$件物品装入容量为$j$的背包的最大价值,则表示为$dp[i-1][j-c[i]]$,再加上当前物品$i$的价值$w[i]$,
- 不装入背包:那么问题就转换成前$i-1$件物品放入容量为$j$的背包的最大价值,则$dp[i-1][j]$
1 | /** |
0-1背包的空间优化
回顾 01 背包的基本解法的状态转移方程,如下:
1 | dp[i][j] = dp[i - 1][j] 当前物品不选 |
空间复杂度为 $O(NV)O(NV)$,状态转移中 i 这一维只跟 i - 1 有关系,因此 i 这一维用滚动数组至少可以将 N 行优化为 2 行。这本来是动态规划的基本操作,在线性动态规划中很常见。这里特别提空间优化是因为 01 背包的 i 这一维用 1 行就可以解决。
1 | 假设状态只有一行,即 dp[j] := 占用了 j 空间的情况下可以取到的最大价值, 在推第 i 行的时候,dp 数组中存的是第 i - 1 行的信息。 |
看状态的两个转移方向,第一个是 $dp[i - 1][j]$,这刚好就是当前 $dp $数组在 $j $位置保存的数据,因此不用动,比较麻烦的是另一个,就是 $dp[i - 1][j - v[i]] + w[i]$。这里要用到第 $i - 1 $行的 $dp[j - v[i]]$,但是如果按照正常的 $j $从 $0$ 到 $V$ 推的话,计算 $dp[j]$ 的时候,$dp[j - v[i]]$ 保存的已经是第 $i $行信息了。
因此这里需要转换一下,从大往小推,推到 $dp[j]$ 时,$dp[j+1], dp[j+2],…,dp[V]$ 都已经是第$i$行的信息了,但是它们对 $dp[j] $的计算没有影响,有影响的 $dp[j-v[i]]$ 此时还是第 $i - 1$ 行的信息,可以满足转移方程$dp[i - 1][j - v[i]] + w[i] $的需要。因此当空间这一维的状态从大往小推的时候,$i$ 这一维状态可以优化到一维。
这就是 01 背包的终极形态了。
1 | dp[j] = max(dp[j], dp[j - v[i]] + w[i]) // j 从大往小推 |
完整代码:
1 | /** |
2 完全背包
题目:有N件物品和一个容量为V的背包。第i建物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大(每件物品可以被无限次使用)
与0-1背包不同的是,完全背包中的每个物品可以被无限次使用。
状态转移方程:$dp[i][j]=max(dp[i-1][j],dp[i][j-c[i]]+w[i])$
- 不装入背包:则问题转化成从前$i-1$个物品所能用的容量是$j$,则有$dp[i-1][j]$
- 装入背包:在0-1背包中,当取第$i$个物品时,可以知道前$i-1$个物品所能用的最大容量是$j-c[i]$,完全背包中每个物品可以取无限次,所以如果取第$i$个物品,那么前$i$个物品所能用的容量还是$j-c[i]$,$dp[i][j-c[i]]+w[i]$。
1 | /** |
完全背包的空间优化
考虑空间优化后的状态转移方程如下:在 01 背包中,由于推第 $i$ 行的 $dp[j]$ 时需要第 $i - 1$ 行的 $dp[j-v[i]] $,因此忌讳在推导 $dp[j]$ 时,$dp[j-v[i]]$ 已经更新过了, 这是 01 背包不希望发生的事,解决的办法就是 $j$ 从大往小推。
而上述 01 背包不希望发生的事正是完全背包希望发生的,即推导第 $i$ 行的 $dp[j]$ 时,用到第 $i$ 行的 $dp[j -v[i]]$ 。而这仅需要把 01 背包中的从大往小推 $j$ 改为从小往大推 $j$ 即可实现。这就是完全背包的优化巧妙的地方。
$dp[j] = max(dp[j], dp[j - v[i]] + w[i]) $
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/dynamic-programming-2-plus/5253i5/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1 | /** |
3 多重背包问题
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
思路 1 :将物品展开,全拆成 01
这与完全背包的朴素思路一致。与完全背包的情况一样,这种方法是正确的,但是时间复杂度较高。
多重背包的朴素算法与完全背包的朴素算法没有区别,而多重背包的优化相对较难,并且力扣上面没有多重背包的题目。下面仅对多重背包的优化做简单的介绍。
思路 2 :2 进制分解
这是对思路 1 的优化。
有这样一个事实:任意一个数 n,它一定可以用 1,2,4,8,… $2^{k}$ ,以及 $2^{k}$到 $2^{k+1}$之间的某个数表示。例如 13 以内的所有数都可以用 1,2,4,6 表示。所以对于物品 $i$, 数量限制是$c_{i}$, 可以将其分成若干物品,它们的价值和体积为:($w_{i}$ ,$v_{i}$),($2w_{i}$,$2v_{i}$),…
然后对这些物品做 01 背包。这样 01 背包的物品数就比思路 1 少很多了。这可以理解为类似于倍增法的思想。倍增法超出了力扣的范围,感兴趣的话可以找相关的资料学习。
以上就是背包动态规划的基本内容。背包动态规划在力扣上题目不多,下一节整理了 8 道背包动态规划的练习题,通过这些题可以大致了解背包问题的一些经典问题和常见的问法。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/dynamic-programming-2-plus/5253i5/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
写在最后
欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。