相对于0-1背包,主要区别点在于物品可以使用无限次
0-1背包的dp状态转移方程
// 01背包
for (int i = 0; i < weight.length; i++) {
// 从后往前遍历背包容量
for (int j = cap; j >= weight[i]; j--) {
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
}
}完全背包的dp状态转移方程
// 完全背包
for (int i = 0; i < weight.length;; i++) {
// 从前往后遍历背包容量
for (int j = weight[i]; j <= cap; j++) {
dp[j] = max(dp[j], dp[j-weight[i]] + valeu[i]);
}
}上面那个是先遍历物品在遍历背包容量
我们还可以从另外一个角度理解完全背包:
因为所有物品可以无限次拿,定义dp数组表示 容量为n时,所装物品的最大价值
则有 f(n) = max( f(n-i) ) 0<i<=n且需要存在物品重量等于i
则可以循环物品,针对每一个小于等于n的物品,计算f(n-i)值取最大值即
// 先循环容量、在循环物品
for (int i = 1; i <= cap; i++) {
for (int j = 0; j < weight.length; j++) {
if (weight[j] <= i) {
dp[i] = Math.max(dp[i], dp[i - weight[j]] + values[j]);
}
}
}但是两种在使用的时候也有点区别
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[amount];
}
}完全背包求方案个数,要求不考虑所选物品的顺序,即求“组合数”
这种完全背包问题就只能 先循环物品在循环背包
因为相同容量的情况下,针对两种物品如果考虑顺序就有两种方案(先选择a在选择b或先选择b在选择a),不考虑顺序就一种(一个a一个b)
class Solution {
public int combinationSum4(int[] nums, int target) {
// dp:当目标值为n的时候数组中的组合方案数
// 存在递推公式:f(n) = sum( f(i) ) 0<=i<n;前提条件是 nums中要存在 n-i
// 即当数组中的元素小于等于n时,f(n-i)的和
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 1; i <= target; i++) {
for (int num : nums) {
if (num <= i) {
dp[i] += dp[i-num];
}
}
}
return dp[target];
}
}与上题刚好相对,考虑所选物品的顺序,即求“排列数”
这种完全背包问题就只能 先循环背包早循环物品
针对每一个i<n,累计f(n-i)
class Solution {
public int coinChange(int[] coins, int amount) {
// 动态规划,申明dp:表示总金额n的最少硬币个数
// 存在递推公式 f(n) = min( f(i)+1 ) 0<=i<n; 前提条件是 存在n-i面额的硬币
// 也就是 组成f(i)的最少硬币数+一枚n-i面额的硬币
// 即 遍历每个硬币作为n-i存在,在加上f(i)
int[] dp = new int[amount+1];
Arrays.fill(dp, Integer.MAX_VALUE-1);
dp[0]=0;
for(int i=1; i<=amount; i++){
for(int coin: coins){
if(coin<=i){
dp[i]=Math.min(dp[i-coin]+1,dp[i]);
}
}
}
return dp[amount]==Integer.MAX_VALUE-1?-1:dp[amount];
}
}完全背包问题求最值
最值问题,先遍历容量在遍历物品,或者先遍历物品在遍历容量,都可以
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp, Integer.MAX_VALUE-1);
dp[0]=0;
for(int i=0; i<coins.length; i++){
for(int j=coins[i]; j<=amount; j++){
// 先遍历物品在遍历容量,求最值
dp[j]=Math.min(dp[j], dp[j-coins[i]]+1);
}
}
return dp[amount]==Integer.MAX_VALUE-1?-1:dp[amount];
}class Solution {
public int numSquares(int n) {
/*
完全背包问题,物品是完全平方数 1、4、9、16,背包容量是n
存在 f(n) = min( f(n-i) ) i是小于等于n的完全平方数
*/
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j*j <= i; j++) {
if (dp[i] == 0 || dp[i] > dp[i - j*j] + 1) {
dp[i] = dp[i - j*j] + 1;
}
}
}
return dp[n];
}
}完全背包问题求最值
最值问题,先遍历容量在遍历物品,或者先遍历物品在遍历容量,都可以
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int len = s.length();
// 完全背包:dp数组表示:是否能组成 s中前n个字符组成的字符串
// 则有递推公式:dp[n] = true( dp[n-i] ) 0<i<=n 且需要 字串sub(n-i,n)要在字典里面
boolean[] dp = new boolean[len + 1];
dp[0] = true;
for (int i = 1; i <= len; i++) {
String str = s.substring(0, i);
for (String word : wordDict) {
// 直接遍历物品,判断如果字典中元素是当前字符的后缀,则取dp[n-word.length]
if (str.endsWith(word)) {
dp[i] = dp[i - word.length()] || dp[i];
}
}
}
return dp[len];
}
}完全背包求是否有值
类似于最值,先遍历容量在遍历物品,或者先遍历物品在遍历容量,都可以
针对完全背包问题:
主要就是在求解方案数的时候,要看下是“排列数”还是“组合数”,选择正确的遍历顺序
如果是求解最值等问题,两种遍历顺序都可以
具体看怎么容易理解,怎么容易解决问题