这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战
今天来讲讲经典的背包问题,相对上次说的子序列问题,背包问题的状态和选择会更清晰,一般能在题目中获得信息。
0-1背包
题目描述如下:
给你一个可装载重量为W的背包和N个物品,每个物品都有重量和价值两个属性。其中第i个物品的重量为wt[i],价值为val[i],现在让你用这个背包装物品,最多能装的价值是多少?
阅读完之后我们可以明确出这道题目的状态就是“背包的容量”和“可选择的物品”,选择就是把不把物品装进背包里。根据我们的两个状态,可以定义出以下dp数组:
dp[i][w]:对于前i个物品,当前背包容量为w,这种情况下可以装的最大价值是dp[i][w]。 那么dp[i][w]怎么推出来呢?取决于第i个物品选择与否,可以得出如下状态转移方程:
dp[i][w]=Math.max(dp[i-1][w-wt[i]]+val[i],dp[i-1][w])
(dp[i-1][w-wt[i]]+val[i]是选择将第i个物品放入背包,dp[i-1][w]是选择不将第i个物品放入背包)
而base case就是dp[0][w]和dp[i][0]都是0。 我们不难写出代码:
//函数参数W表示背包容量,N表示物品个数,wt是每样物品的重量数组,val是每样物品的价值数组。
function bagProblem(W,N,wt,val){
let dp=Array(N+1);
for(let i=0;i<=N;i++){
dp[i]=Array(W+1).fill(0);
}
for(let i=1;i<=N;i++){
for(let w=1;w<=W;w++){
if(w-wt[i-1]<0)dp[i][w]=dp[i-1][w];
else dp[i][w]=Math.max(dp[i-1][w],dp[i-1][w-wt[i-1]]+val[i-1]);
}
}
return dp[N][W];
}
完全背包问题
完全背包数组和0-1背包的区别在于0-1背包的所谓0-1就是物品要么放要么不放,物品数量只有一个;而完全背包问题的特殊性在于物品的数量是无限的。来看一道完全背包问题的变种——零钱兑换II。
力扣链接 之所以说它是完全背包问题,是因为它可以转化成以下的描述形式: 一个背包的最大容量是amout(就是要凑成的总金额),你有一系列物品(就是硬币),物品的数量是无限的,每个物品的重量为coins[i],求把背包装满的方法。
状态和选择和上一道0-1背包问题是一样的,我们下面来定义以下dp数组: dp[i][j]表示只使用前i个物品,凑出容量j,有dp[i][j]种方法。 如果我们不选择第i个物品,那么dp[i][j]应该等于dp[i-1][j];如果我们选择了第i个物品,那么dp[i][j]应该等于dp[i][j-coins[i]]。所以可得以下状态转移方程
dp[i][j]=dp[i-1][j]+dp[i][j-coins[i]];
base case就是dp[0][j]=0,dp[i][0]=1; 再把完整代码写出来(一遍过):
var change = function(amount, coins) {
let dp=Array(coins.length);
for(let i=0;i<=coins.length;i++){
dp[i]=Array(amount+1).fill(0);
}
for(let i=0;i<=coins.length;i++){
dp[i][0]=1;
}
for(let i=1;i<=coins.length;i++){
for(let j=1;j<=amount;j++){
if(j-coins[i-1]>=0){
dp[i][j]=dp[i-1][j]+dp[i][j-coins[i-1]];
}
else dp[i][j]=dp[i-1][j];
}
}
return dp[coins.length][amount];
};
小小结尾
其实我们遇到更多的会是背包的变种问题,所以需要看到本质,将它们转化为背包问题来解决。