假设:背包最大重量为4,我们有三个物品。重量和价值如下表所示:
| weight | value | |
|---|---|---|
| item0 | 1 | 15 |
| item1 | 3 | 20 |
| item2 | 4 | 30 |
明确dp数组的含义
即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
dp[i][j]表示:从下标为[0-i]的物品里随意取,放进容量为j的背包里,价值总和最大是多少?
用图示的方式画出来,j=0也就是红框中背包容量=0的时候,这个时候肯定啥也装不下,所以格子里全都是0.
此时来看i=0的情况,也就是物品0。重量1~4的背包都能放下它,所以这里可以更新价值为物品0的价值15.
接下来,看物品1的情况。也就是dp[1][j]这一行的更新。
很明显,物品1的重量是3,背包1放不下,所以背包1还是保留它之间的状态,也就是只装物品0。
这句话我们用公式表示就是dp[i][j]=dp[i-1][j],意思就是这个重量j背包的上一个状态转移到现在的背包。
对于重量2的背包也是一样!因为它也装不下物品1.
现在来到重量3的背包,它装得下物品1,但是它只能装下物品1,这个时候就要取舍了,是装物品1还是装其他的物品价值大呢?装物品1的价值是20,装物品0的价值是15,那肯定选择装物品1.
这个选择的过程的体现是直接:dp[i][j]=Math.max(dp[i-1][j],value[i])就好了吗?
当然不是! 在这里我们很直观的直接选择了物品1的价值,因为背包刚好能装下。
我们看下一种情况:
当背包重量4的时候,完全可以放下重量等于3的物品1,甚至还有一个重量单位的剩余空间! 那么怎么办呢?既然有剩余,我们就去找这个重量1的物品就好了,但是我们换一种方式,我们不去物品列表里搜索重量1的,我们去dp表里搜索重量1的背包的价值就好了,因为重量1的背包肯定只能装重量1的物品,所以我们去找重量1背包的状态就好了。
这才是最后的公式
dp[i-1][j-weight[i]]=dp[0][4-3]=dp[0][1]=15.
dp[i-1][j]=dp[i-1][j-weight[i]]+value[i]
在看完上述的过程,梳理出递归公式了吗? 分为多种情况吧
- 背包放得下吗?
- 放不下:dp[i][j]=[dp[i-1][j]
- 放得下:拿还是不拿?
- 拿:dp[i][j]=dp[i-1][j-weight[i]]+value[i]
- 不拿:dp[i][j]=dp[i-1][j]
实际上放不下的情况和放得下但是不拿的情况,公式是一样的,我们可以总结成一个公式:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
总结一下:
最难的一点就是关于拿还是不拿的决策,要理解这个公式dp[i-1][j-weight[i]]还是挺难的。 我们把j想象成当前背包的空间,而weight[i]是物品i的重量,当物品放进背包里的时候占了空间,那就挤占了别的物品的空间,背包还可以装下物品的空间就被压缩了,剩下j-weight[i]的空间给别的物品了。