1.0-1背包类型
从有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
递推公式分为两种:
第一种:dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);//力扣Number416 //分割等和子集、最后一块石头的重量等 一维dp数组无须初始化 //对于一维dp数组 j的含义是背包的容量 dp[j]表示容量为j的背包最大能容纳的价值 //一维dp数组遍历时需要从后向前遍历 for (int i = 0; i < wLen; i++){ for (int j = bagWeight; j >= weight[i]; j--){//注意这里的j的范围 dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); } } //对于二维dp数组 初始化第一列全为0 第一行从weight[0]开始往后 dp[0][j]都是weight[0] //i表示从物品0-i中任意挑选 容量为j的背包 dp[i][j]表示能装的最大价值 //二维dp从前往后遍历 for (int i = 1; i < weight.length; i++) { for (int j = 1; j <= bagSize; j++) { if (j < weight[i]) { /** * 当前背包的容量都没有当前物品i大的时候,是不放物品i的 * 那么前i-1个物品能放下的最大价值就是当前情况的最大价值 */ dp[i][j] = dp[i-1][j]; } else { /** * 当前背包的容量可以放下物品i * 那么此时分两种情况: * 1、不放物品i * 2、放物品i * 比较这两种情况下,哪种背包中物品的最大价值最大 */ dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]); } } }
第二种:dp[j] += dp[j - nums[i]];
注意:求装满背包有几种方法类似的题目,递推公式基本都是这样的。
如number494目标和问题:给一个数组,一个target,求通过改变+ - 号让结果等于target的种类
//j的含义为容量为j的背包 dp[j]的含义为容量为j的背包装的价值为target的种类数 //一维dp初始化dp[0] = 1; int[] dp = new int[target]//dp[target]表示容量为target的背包能装的种类数(价值) //递推公式:dp[j] += dp[j - nums[i]]; //对于0-1背包问题 这种递推公式下的遍历方式为从后向前 for (int i = 0; i < nums.length; i++) { for (int j = bagSize; j >= nums[i]; j--) { dp[j] += dp[j - nums[i]]; } }
2.完全背包问题
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
//完全背包核心代码 // 先遍历物品,再遍历背包 for(int i = 0; i < weight.size(); i++) { // 遍历物品 for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }
遍历顺序:排列与组合
for (int i = 0; i < coins.size(); i++) { // 遍历物品 for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量 dp[j] += dp[j - coins[i]]; } }
假设:coins[0] = 1,coins[1] = 5。
那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。
所以这种遍历顺序中dp[j]里计算的是组合数!
如果把两个for交换顺序,代码如下:
for (int j = 0; j <= amount; j++) { // 遍历背包容量 for (int i = 0; i < coins.size(); i++) { // 遍历物品 if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]]; } }
背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。
此时dp[j]里算出来的就是排列数!
可能这里很多同学还不是很理解,建议动手把这两种方案的dp数组数值变化打印出来,对比看一看!(实践出真知)
public static void main(String[] args) { int[] nums = {1,2,5}; System.out.println(change(5, nums)); } public static int change(int amount, int[] coins) { //组合:先物品 后背包 int[] dp =new int[amount + 1];//容量为amount的背包能装的种类数(价值) dp[0] = 1; for (int i = 0; i < coins.length; i++) { for (int j = coins[i]; j < dp.length; j++) { dp[j] += dp[j - coins[i]]; System.out.print(dp[j] + " "); } System.out.println(); } //排列:先背包 后物品 int[] dp1 = new int[amount+1]; dp1[0] = 1; for (int j = 0; j <= amount; j++) { // 遍历背包容量 for (int i = 0; i < coins.length; i++) { // 遍历物品 if (j - coins[i] >= 0) { dp1[j] += dp1[j - coins[i]]; System.out.print(dp1[j] + " "); } } System.out.println(); } return dp[amount]; }
参考文献: