直面动态规划之实训篇(二)

181 阅读2分钟

这是我参与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];
};

小小结尾

其实我们遇到更多的会是背包的变种问题,所以需要看到本质,将它们转化为背包问题来解决。