0-1背包问题是一个基本问题,基于这个基本问题,可以衍生出千姿百态的变种问题,这种题目就非常适合拿来构造解题模型。
0-1背包问题说的是这么回事儿:
有 n 件物品,物品体积用一个名为 w 的数组存起来,物品的价值用一个名为 value 的数组存起来;每件物品的体积用 w[i] 来表示,每件物品的价值用 value[i] 来表示。现在有一个容量为 c 的背包,问你如何选取物品放入背包,才能使得背包内的物品总价值最大?
注意:每种物品都只有1件
思路分析
这道题如果全靠本能来做,相信不少同学会联想到“暴力枚举法”:暴力枚举每一件物品放或者不放进背包的情况。考虑到每一种物品都面临“放”和“不放”两种选择,因此 n
个物品就对应 2^n
种情况,进而会带来高达 O(2^n)
的时间复杂度。这个时间复杂度是众多复杂度中相对来说比较恐怖的“指数量级”,我们是万万不能让这种东西出现在面试题解中的,因此果断放弃它。
遇到最值问题,一定要在可能的解题方案中给动态优化留下一席之地。事实上,背包系列问题,正是动态规划的标准对口问题。
“倒推”法明确状态间关系
现在,假设背包已满,容量已经达到了 c
。站在c
这个容量终点往后退,考虑从中取出一样物品,那么可能被取出的物品就有 i
种可能性。我们现在尝试表达“取出一件”这个动作对应的变化,我用 f(i, c)
来表示前 i
件物品恰好装入容量为 c
的背包中所能获得的最大价值。现在假设我试图取出的物品是 i
,那么只有两种可能:
- 第
i
件物品在背包里 - 第
i
件物品不在背包里for(let i=1;i<=n;i++) {
for(let v=w[i]; v<=c;v++) {
dp[i][v] = Math.max(dp[i-1][v], dp[i-1][v-w[i]]+value[i])
}
}
背包问题完整求解代码
// 入参是物品的个数和背包的容量上限,以及物品的重量和价值数组
function knapsack(n, c, w, value) {
// dp是动态规划的状态保存数组
const dp = (new Array(c+1)).fill(0)
// res 用来记录所有组合方案中的最大值
let res = -Infinity
for(let i=1;i<=n;i++) {
for(let v=c;v>=w[i];v--) {
// 写出状态转移方程
dp[v] = Math.max(dp[v], dp[v-w[i]] + value[i])
// 即时更新最大值
if(dp[v] > res) {
res = dp[v]
}
}
}
return res
}