N件物品和一个最多能背重量为W的背包。第i件物品的重量为weight[i],得到的价值是value[i]。每件物品都有无限个(可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
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]);}
}
leetcode题目
- [518. 零钱兑换 II](https://leetcode-cn.com/problems/coin-change-2/)
- [377. 组合总和 Ⅳ](https://leetcode-cn.com/problems/combination-sum-iv/)
- 爬楼梯变形
- [322. 零钱兑换](https://leetcode-cn.com/problems/coin-change/)
- [279. 完全平方数](https://leetcode-cn.com/problems/perfect-squares/)
- [139. 单词拆分](https://leetcode-cn.com/problems/word-break/)
518. 零钱兑换 II
这一题与https://blog.csdn.net/qq_42604176/article/details/116051902?spm=1001.2014.3001.550101背包中的组合问题有相似之处。只需要把遍历顺序修改就行了。
step1: dp[j]:凑成总金额j的货币组合数为dp[j]
step2: dp[j] += dp[j - coins[i]];
step3: 凑成总金额0的货币组合数为1。
step4:使用完全背包的遍历方式,注意由于是求组合数,内外嵌套需要注意。
AC代码如下:
class Solution {
public:int change(int amount, vector<int>& coins) {int sum = 0;vector<int> dp(amount+1,0);dp[0]=1;for(int i = 0; i < coins.size(); i++){for(int j = coins[i]; j <= amount; j++){dp[j] += dp[j-coins[i]];}}return dp[amount];}
};
代码随想录公众号做出的这个总结很好,虽然现在还没有完全理解是为什么:
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
377. 组合总和 Ⅳ
如果本题要把排列都列出来的话,只能使用回溯算法爆搜。
但是它只要求我们得到排列方法的数目。由于可以重复取,所以使用完全背包。由于是求排列数,外层for循环遍历背包,内层for循环遍历元素。
最内层需要对两个数相加小于INT_MAX进行限制。
class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(target+1,0);dp[0] = 1;for(int i = 0; i <= target; i++) //遍历背包容量{for(int j = 0; j < nums.size(); j++) //遍历物品{if(i >= nums[j] && dp[i] < INT_MAX - dp[i-nums[j]])dp[i] += dp[i-nums[j]]; }}return dp[target];}
};
爬楼梯变形
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次可以爬 1 、 2或者m 个台阶。问有多少种不同的方法可以爬到楼顶呢?
分析:1阶,2阶,m阶就是物品,楼顶就是背包。每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。所以这是一个完全背包问题。
step1 : dp[i]爬到有i个台阶的楼顶,有dp[i]种方法。
step2: dp[i] += dp[i-j];
step3: dp[0] = 1,dp[…]=0;
step4:遍历顺序,由于是排列问题,1、2与2、1是不一样的,所以将背包容量放在外层循环,将物品放到内层循环。又因为是完全背包问题,所以从前向后遍历。
int climbStairs(int n,int m)
{vector<int> dp(n+1,0);dp[0]=1;for(int i = 0; i <= n; i++){for(int j = 0; j <= m; j++)if(i -j >= 0) dp[i] += dp[i-j];}return dp[n];
}
322. 零钱兑换
step1:dp[j]凑足金额为j所需要的钱币的最少个数为dp[j]
step2:dp[j] = min(dp[j-coins[i]]+1,dp[j]);
step3:凑足金额为0的所需金币个数为0,即dp[0]=0;其他初值为INT_MAX。
凑足金额为j-coins[i]的最少个数为dp[j-coins[i]],那么只需要加上一个钱币coins[i]即dp[j-coins[i]]+1就是dp[j].
step4:求最小个数,钱币有顺序无顺序都可以,都不影响钱币的最小个数。
求组合数,for外层遍历物体,for内层遍历背包。求排列,外层for遍历背包,内层for循环遍历物品。
本题两者均可。
class Solution {
public:int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount+1,INT_MAX);dp[0] = 0;for(int i = 0; i < coins.size(); i++) //物品{for(int j = coins[i]; j <= amount; j++) //背包容量{//如果为初值,跳过,没有必要计算,而且计算会溢出if(dp[j-coins[i]] == INT_MAX) continue;dp[j] = min(dp[j-coins[i]]+1,dp[j]);}}//如果没有被赋值,那么说明没有解,返回-1if(dp[amount] == INT_MAX) return -1;return dp[amount];}
};
279. 完全平方数
完全平方数就是物品(无限使用),凑成的正整数n就是背包。问凑满背包最少有多少个物品。
step1:dp[j],凑满容量为j的背包最少物品数目。
step2:dp[j] = min(dp[j-i*i] +1,dp[j]);
step3: dp[0] = 0;虽然0符合完全平方数要求,但是题目要求是从1开始找完全平方数。其余下标的dp初始值为INT_MAX
step4:对于求最小方法数的,内外层不需要管循环。
class Solution {
public:int numSquares(int n) {vector<int> dp(n+1,INT_MAX);dp[0] = 0;for(int i = 1; i <= n; i++){for(int j = i*i; j <= n; j++){if(dp[j-i*i] == INT_MAX) continue;dp[j] = min(dp[j-i*i]+1,dp[j]);}}return dp[n];}
};
139. 单词拆分
物品:wordDict中的string,可以重复取
背包:s字符串
step1: dp[i]:字符串长度为i,dp[i]为true,表示可以拆分一个或者多个在字典中出现的单词。
step2: 如果dp[j]为true,则[j,i]这个区间的子串出现在字典里,那么dp[i]一定为true。
所以递推公式为;
if(wordSet.find(s.substr(j,i-j)) != wordSet.end() && dp[j]) dp[i] = true;
step3:dp[0]字符串为0,为了方便推导公式,我们只好给他true,不然公式推导下去全为false。
step4:由于是求最小方法数,所以内外层遍历顺序无需在意
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {//将vector放入set中,加快查询unordered_set<string> wordSet(wordDict.begin(),wordDict.end());vector<bool> dp(s.size()+1,false);dp[0] = true;for(int i = 0; i <= s.size(); i++) //背包容量{for(int j = 0; j <= i; j++) //物品{string str = s.substr(j,i-j); if(wordSet.find(str) != wordSet.end() && dp[j] == true){dp[i] = true;}}}return dp[s.size()];}
};