代码随想录算法训练营第四十四天 _ 动态规划_完全背包问题、518.零钱兑换II、377.组合总和IV。
学习目标:
动态规划五部曲:
① 确定dp[i]的含义
② 求递推公式
③ dp数组如何初始化
④ 确定遍历顺序
⑤ 打印递归数组 ---- 调试
引用自代码随想录!
60天训练营打卡计划!
学习内容:
完全背包问题 – 二维dp数组
- 动态规划五步曲:
① 确定dp[i][j]的含义 : 任取[0, i]的物品(可重复使用)后放进容量为j的背包 所能放的 最大价值
② 求递推公式 : dp[i][j] = Math.max(dp[i-1][j], dp[i][j-weight[i]] + value[i]);
Ⅰ 不放物品 i : dp[i][j] = dp[i-1][j];
Ⅱ 放物品 i : dp[i][j-weight[i]] + value[i] 依赖本行已填充的值
③ dp数组如何初始化 : 对第一行进行单独的初始化,如下图所示。当背包容量大于第一个物品的重量时开始放入背包,一直连续放入。
④ 遍历顺序:先遍历物品,再遍历背包,和01背包问题一致。
下面的代码应该是没问题的,但是数字量变大之后会很容易超时。
// 动态规划
import java.util.Scanner;public class Main {public static void main(String[] args) {//m,n分别代表物品种类和背包容量int itemSize = 0,bagSize = 0;Scanner sc = new Scanner(System.in);//获取itemSize和bagSize的值itemSize = sc.nextInt();bagSize = sc.nextInt();//初始化对应的重量数组和价值数组int[] weight = new int[itemSize];int[] value = new int[itemSize];//这两个都是物品的属性,大小只和物品数量有关for(int i = 0;i < itemSize;i++){weight[i] = sc.nextInt();value[i] = sc.nextInt();}// 完全背包问题testWeightBagProblem(weight,value,bagSize);}/*** 动态规划获得结果* @param weight 物品的重量* @param value 物品的价值* @param bagSize 背包的容量*/public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){int itemSize = weight.length;// dp数组的含义是:在[0,i]件物品中选择是否放入背包 的 最大价值int[][] dp = new int[itemSize][bagSize+1];int val = 0;// 初始化dp数组,默认都为0.// 使用最小重量的物品为该物品所在行赋值for(int j = weight[0]; j < bagSize+1; j++){if(j % weight[0] == 0) val += value[0];dp[0][j] = val;}// 正常的为dp数组赋值,依赖左上位置的其他的dp值for(int i = 1; i < itemSize; i++){// j是背包容量for(int j = 1; j < bagSize+1; j++){// 如果容量可以放入新的物品,则从本行的左侧继承if(j >= weight[i]){// 一定要填j < weight[i]的位置,// dp[i][j-weight[i]]与该行左侧的值息息相关。dp[i][j] = Math.max(dp[i-1][j], dp[i][j-weight[i]] + value[i]);}// 如果容量不够放入新的物品,则从上一行继承else if(j < weight[i]) dp[i][j] = dp[i-1][j];}}System.out.println(dp[itemSize-1][bagSize]);// 打印dp数组for (int i = 0; i < itemSize; i++) {for (int j = 0; j <= bagSize; j++) {System.out.print(dp[i][j] + "\t");}System.out.println("\n");}}
}
完全背包问题 – 一维dp数组
- 动态规划五步曲:
① 确定dp[j]的含义 : 任取[0, i]的物品(可重复使用)后放进容量为j的背包 所能放的 最大价值
② 求递推公式 : dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
Ⅰ 不放物品 i : dp[j]
Ⅱ 放物品 i : dp[j-weight[i]] + value[i]
③ dp数组如何初始化 : 初始化为0
④ 遍历顺序:先遍历物品,再遍历背包(正序),和01背包问题8同。
虽然下面的代码应该是没问题的,但是数字量变大之后会很容易超时。
import java.util.Scanner;public class Main {public static void main(String[] args) {//m,n分别代表物品种类和背包容量int itemSize = 0,bagSize = 0;Scanner sc = new Scanner(System.in);//获取itemSize和bagSize的值itemSize = sc.nextInt();bagSize = sc.nextInt();//初始化对应的重量数组和价值数组int[] weight = new int[itemSize];int[] value = new int[itemSize];//这两个都是物品的属性,大小只和物品数量有关for(int i = 0;i < itemSize;i++){weight[i] = sc.nextInt();value[i] = sc.nextInt();}// 完全背包问题testWeightBagProblem(weight,value,bagSize);}/*** 动态规划获得结果* @param weight 物品的重量* @param value 物品的价值* @param bagSize 背包的容量*/public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){int itemSize = value.length;int[] dp = new int[bagSize+1];// 初始化为0// 递归推导for(int i = 0; i < itemSize; i++){for(int j = weight[i]; j < bagSize+1; j++){dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);// System.out.print(dp[j] + "\t");}// System.out.println("\n");}System.out.print(dp[bagSize] + "\n");}
}
518.零钱兑换II
- 动态规划五步曲:
① 确定dp[i]的含义 : 装满容量为i的背包有dp[i]种方法。
② 求递推公式 : dp[j] += dp[j - coins[i]] (装满背包的方法数的相关问题的统一递归公式)
③ dp数组如何初始化 : dp[0] = 1 (若dp[0] = 0,则所有的全部为0。)
④ 确定遍历顺序 : 从前向后,先物品后背包
⑤ 打印dp数组
组合的dp数组
1 1 1 1 1
1 2 2 3 3
1 2 2 3 4排列的dp数组
1 1 1
1 1 1
1 2 2
2 3 3
3 5 5
5 8 9
- 一种记忆方法:先遍历物品(物品不会重复被取得)为组合
class Solution {public int change(int amount, int[] coins) {int[] dp = new int[amount+1];int itemSize = coins.length;// 初始化dp[0] = 1;// 递归推导for(int i = 0; i < itemSize; i++){// 先物品后背包就可以保证是组合而不是排列for(int j = coins[i]; j < amount+1; j++){// 因为减去coins[i]的面值,就是可以凑出的方法数。dp[j] += dp[j - coins[i]];System.out.println(dp[j]);}System.out.println("*");}// for(int j = 0; j < amount+1; j++){// // 先背包后物品就可以保证是排列而不是组合// // for(int j = i; j < amount+1; j++){// for(int i = 0; i < itemSize; i++){// // 因为减去coins[i]的面值,就是可以凑出的方法数。// if(j - coins[i] >= 0)// dp[j] += dp[j - coins[i]];// System.out.println(dp[j]);// }// System.out.println("*");// }return dp[amount];}
}
377.组合总和IV
- 动态规划五步曲:
① 确定dp[i]的含义 : 装满容量为i的背包有dp[i]种方法(排列)。
② 求递推公式 : dp[j] += dp[j - coins[i]] (装满背包的方法数的相关问题的统一递归公式)
③ dp数组如何初始化 : dp[0] = 1 (若dp[0] = 0,则所有的全部为0。)
④ 确定遍历顺序 : 从前向后,先背包后物品
⑤ 打印dp数组
class Solution {public int combinationSum4(int[] nums, int target) {int[] dp = new int[target+1];int itemSize = nums.length;Arrays.sort(nums);// 初始化dp[0] = 1;// 递归推导for(int j = 0; j < target+1; j++){// 先背包后物品就可以保证是排列而不是组合for(int i = 0; i < itemSize && j >= nums[i] ; i++){// 因为减去coins[i]的面值,就是可以凑出的方法数。dp[j] += dp[j - nums[i]];// System.out.println(dp[j]);}// System.out.println("*");}return dp[target];}
}
学习时间:
- 上午两个半小时,整理文档半小时。