Complete Knapsack Problem
2023-11-27 21:14:38
# Algorithm
完全背包和0-1背包的区别
完全背包和0-1背包的区别在于,完全背包是同一个物品可以选很多次。
在求完全背包情况下刚好装满背包的最大值:dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-k*weights[i]]+k*values[i])
这里就是两种情况,选这个物品k次或者不选这个物品。
Leetcode 322 Coin Change
关于初始化的问题
当问题要求背包被恰好装满时,我们通过将除了dp[i][0]
之外的所有值设置为-∞
或∞
来实现(根据题目是求最大还是最小值)。dp[i][0]
设为0,因为当amount为0的时候,是有解的。
当问题只要求物品的总重量不超过背包容量时,我们将dp[∗][∗]
的所有值设置为0。
原始解法:三层for循环
我们在写代码的时候,会循环遍历k。我们会从0开始,这样能代表不选到选k次的情况。但注意,需要与上次迭代所得到的答案dp[i][j]
进行比较,所以代码要写成:
1 | for(int k = 0;k<= j/coins[i-1];k++){ |
完整代码如下:
1 | class Solution { |
优化I:两层for循环
需要注意的是,上面所提到的k次是可以被优化掉的。
优化后的式子:dp[i][j] = Math.max(dp[i-1][j], dp[i][j-coins[i]]+values[i])
注意第二个变成了dp[i][j-coins[i]+values[i]
,因为我们用dp[i]
来代替了之前所有的选过的i
的计算(这里用数学方法也能证明,但建议直接理解这个式子)。
因此,我们可以利用已有的dp值来更新dp,而不需要额外的k循环
1 | class Solution { |
优化II:一层for循环
我们发现dp[i][j]
是依赖于左方和正上方的值,因此我们要在计算dp[i][j]
的时候,保证左方和正上方都已经计算出出来
类似于下面这个图:
1 | class Solution { |