完全背包
解题思路
01背包的核心思路,为了保证每个物品仅被添加一次,01背包内嵌的循环是从大到小遍历。
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
完全背包的物品是可以添加多次的,所以要从小到大去遍历。
// 先遍历物品,再遍历背包
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]);
}
}
Java实现
public class CompletePack_I {
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
new CompletePack_I().testCompletePack(weight, value, bagWeight);
}
public void testCompletePack(int[] weight, int[] value, int bagWeight) {
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.length; i++) { // 遍历物品
for (int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
for (int maxValue : dp) {
System.out.println(maxValue + " ");
}
}
}
518.零钱兑换II
力扣题目链接
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
解题思路
- 思路等同于目标和,求装满背包有几种方法,该题的不同在于是完全背包
Java实现
class Solution {
public int change(int amount, int[] coins) {
//递推表达式
int[] dp = new int[amount + 1];
//初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}
377. 组合总和 Ⅳ
力扣题目链接
给你一个由 不同 整数组成的数组 nums
,和一个目标整数 target
。请你从 nums
中找出并返回总和为 target
的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
解题思路
-
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
-
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
-
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!
-
所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
Java实现
class Solution_LC373 {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 0; i <= target; i++) {
for (int j = 0; j < nums.length; j++) {
if (i >= nums[j]) {
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
}
爬楼梯完全背包
一步一个台阶,两个台阶,三个台阶,…,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?
解决思路
- dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。
- 求装满背包有几种方法,递推公式一般都是
dp[i] += dp[i - nums[j]]
;
Java实现
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
int[] weight = {1,2};
dp[0] = 1;
for (int i = 0; i <= n; i++) {
for (int j = 0; j < weight.length; j++) {
if (i >= weight[j]) dp[i] += dp[i - weight[j]];
}
}
return dp[n];
}
}
322. 零钱兑换
力扣题目链接
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。
你可以认为每种硬币的数量是无限的。
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
解题思路
-
dp[j]:凑足总额为j所需钱币的最少个数为dp[j]
-
凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i]),所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
-
dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。
Java实现
class Solution_LC322 {
public int coinChange(int[] coins, int amount) {
int max = Integer.MAX_VALUE;
int[] dp = new int[amount + 1];
//初始化dp数组为最大值
Arrays.fill(dp, max);
//当金额为0时需要的硬币数目为0
dp[0] = 0;
for (int i = 0; i < coins.length; i++) {
//正序遍历:完全背包每个硬币可以选择多次
for (int j = coins[i]; j <= amount; j++) {
//只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
if (dp[j - coins[i]] != max) {
//选择硬币数目最小的情况
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
}
return dp[amount] == max ? -1 : dp[amount];
}
}
279.完全平方数
力扣题目链接
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
解题思路
- 完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品?
- dp[j]:和为j的完全平方数的最少数量为dp[j]
- dp[j] = min(dp[j - i * i] + 1, dp[j]);
Java实现
class Solution_LC279 {
public int numSquares(int n) {
int max = Integer.MAX_VALUE;
int[] dp = new int[n + 1];
//初始化
for (int j = 0; j <= n; j++) {
dp[j] = max;
}
//当和为0时,组合的个数为0
dp[0] = 0;
// 遍历物品
for (int i = 1; i * i <= n; i++) {
// 遍历背包
for (int j = i * i; j <= n; j++) {
if (dp[j - i * i] != max) {
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
}
}
return dp[n];
}
}
139.单词拆分
力扣题目链接
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。请你判断是否可以利用字典中出现的单词拼接出 s
。
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
解决思路
- 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。
- 递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。
Java实现
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
HashSet<String> set = new HashSet<>(wordDict);
boolean[] valid = new boolean[s.length() + 1];
valid[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i && !valid[i]; j++) {
if (set.contains(s.substring(j, i)) && valid[j]) {
valid[i] = true;
}
}
}
return valid[s.length()];
}
}
多重背包
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
解决思路
- 就是把每种物品的数量,打包成一个个独立的包。
Java实现
public class MultiPack_I {
public static void main(String[] args) {
// 版本一:改变物品数量为01背包格式
List<Integer> weight = new ArrayList<>(Arrays.asList(1, 3, 4));
List<Integer> value = new ArrayList<>(Arrays.asList(15, 20, 30));
List<Integer> nums = new ArrayList<>(Arrays.asList(2, 3, 2));
int bagWeight = 10;
new MultiPack_I().testMultiPack1(weight, value, nums, bagWeight);
}
public void testMultiPack1(List<Integer> weight, List<Integer> value, List<Integer> nums, int bagWeight) {
for (int i = 0; i < nums.size(); i++) {
while (nums.get(i) > 1) { // 把物品展开为i
weight.add(weight.get(i));
value.add(value.get(i));
nums.set(i, nums.get(i) - 1);
}
}
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.size(); i++) { // 遍历物品
for (int j = bagWeight; j >= weight.get(i); j--) { // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight.get(i)] + value.get(i));
}
System.out.println(Arrays.toString(dp));
}
}
public void testMultiPack2(List<Integer> weight, List<Integer> value, List<Integer> nums, int bagWeight){
// 版本二:改变遍历个数
int[] dp = new int[bagWeight + 1];
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight.get(i); j--) { // 遍历背包容量
// 以上为01背包,然后加一个遍历个数
for (int k = 1; k <= nums.get(i) && (j - k * weight.get(i)) >= 0; k++) { // 遍历个数
dp[j] = Math.max(dp[j], dp[j - k * weight.get(i)] + k * value.get(i));
}
System.out.println(Arrays.toString(dp));
}
}
}
}