完全背包
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
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循环可以颠倒(只有纯完全背包)。
// 先遍历物品,再遍历背包
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]);
}
}
倒序用的是“前面的状态”,这个前面的状态是没有加上当前物品的i-1上一层,而正序比较时用的“前面的状态”是已经遍历过,可能已经加入过物品的状态,即重复添加了当前物品。
518. 零钱兑换 II
You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.
Return the number of combinations that make up that amount. If that amount of money cannot be made up by any combination of the coins, return 0.
You may assume that you have an infinite number of each kind of coin.
Input: amount = 5, coins = [1,2,5]
Output: 4
Explanation: there are four ways to make up the amount:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
明确是多重背包问题,因为每个coin可以取多次,背包物品是coins的元素,背包容量是amount,目的是求装满背包有多少种方法,或凑成总金额的硬币组合数。
- dp[j]:凑成总金额j的货币组合数为dp[j]
- 求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]];
- 初始化:dp[0]一定要为1,dp[0] = 1是 递归公式的基础。
- 遍历顺序:很重要!!!和纯完全背包问题不同,纯完全背包求得装满背包的最大价值是多少,和凑成总和的元素有没有顺序没关系。而本题要求凑成总和的组合数,元素之间明确要求没有顺序(所以遍历顺序需要求组合数的版本)。
问装满背包有几种方法时,遍历顺序很重要
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount+1];
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[dp.length-1];
}
}
时间复杂度: O(mn)
,其中 m 是amount,n 是 coins 的长度
空间复杂度: O(m)
377. 组合总和 Ⅳ
Given an array of distinct integers nums and a target integer target, return the number of possible combinations that add up to target.
Input: nums = [1,2,3], target = 4
Output: 7
The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
Note that different sequences are counted as different combinations.
本题完全背包问题,背包容量为target,物品为nums中的元素,可重复放入多次。
本题求问装满背包有多少种组合/方法,但不同排序代表不同组合。所以是在求排列总和(有多少种不同的排列装满背包)。
dp[i]: 凑成目标正整数为i的排列个数为dp[i]
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target+1];
dp[0] = 1;
for (int j = 0; j <= target; j++) { // 背包
for (int i = 0; i < nums.length; i++) { // 物品
if (j >= nums[i]) dp[j] += dp[j-nums[i]];
System.out.print(dp[j]);
}
}
return dp[target];
}
}
时间复杂度: O(target * n)
,其中 n 为 nums 的长度
空间复杂度: O(target)
求装满背包有几种方法,递归公式都是一样的,没有什么差别,但关键在于遍历顺序!