上一篇文章写了01背包问题,今天写有一点相似的完全背包问题。
什么是完全背包问题
有N件物品和一个容量为W的背包,第i件物品的重量为weight[i],价值为value[i],每件物品都有无限个(可以多次放入背包),求解哪些物品装入背包能使总价值最大。
还是01背包那个例子:
weight = [1, 3, 4]
value = [15, 20, 30]
bagsize = 4
dp数组及下标含义
dp[j]表示背包容量为j时,背包中物品的总价值
递推公式
与01背包相同,dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
初始化
与01背包相同,全初始化为0
遍历顺序(与01背包不同!!!)
先物品再背包,先背包再物品都可以(具体问题具体分析!)
背包和物品都是从小到大(与01背包不同!!!)
疑问:为什么完全背包的背包遍历是从小到大,01背包是从大到小?
答:完全背包的物品是可以多次放入背包的,01背包从大到小正是为了避免物品被多次放入。(具体解释见如何解决01背包问题_weixin_46388493的博客-CSDN博客)
int CompletePack(vector<int> weight, vector<int> value, int bagsize){
vector<int> dp(bagsize + 1, 0);
for(int i = 0; i < weight.size(); i++){
for(int j = weight[i]; j <= bagsize; j++){
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
return dp[bagsize];
}
例子
零钱兑换2
题目:Loading Question... - 力扣(LeetCode)
思路:典型的完全背包问题,重点在本题求是组合数,需要注意遍历顺序!例如:5 = 1 + 2 + 2,5 = 2 + 1 + 2,这个组合数为1,但是排列数为2
dp数组及下标含义
dp[j]表示总金额为j有多少种兑换组合数
递推公式
dp[j] += dp[j - coins[i]](与如何解决01背包问题_weixin_46388493的博客-CSDN博客中的例子“目标和”相同)
初始化
dp[0]应初始化1,不然就是加0,dp数组全为0;其余初始化为0,最开始的方法数应该为0
遍历顺序
结论:先物品再背包,求的是组合数;先背包再物品,求的是排列数
疑问:这个结论是怎么得到的
答:例如coins = [1, 5],先物品再背包,物品的顺序是从小到大的,只会出现[1, 5]的情况;先背包再物品,背包容量为j时,出现[1, 5]的情况,当背包容量为j + 1时,dp数组存入5,就可以出现[5, 1]的情况
int change(int amount, vector<int> &coins){
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];
}
单词拆分
思路:之前的例子都是数组,这道题变成了字符串,换汤不换药,但是递推公式不太容易想到。
dp数组及下标含义
dp[j]表示长度为j的字符串能否拆分
递推公式
如果dp[j] = true,字符串下标(j, i)的字串能在在字符串列表中找到,则dp[i] = true;否则dp[i] = false
初始化
dp[0]初始化为true,否则dp数组全为false
遍历顺序
两种方式都可以,因为本题不是求组合数或排列数
bool wordBreak(string s, vector<string>& wordDict) {
set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> dp(s.length() + 1, false);
dp[0] = true;
for(int i = 0; i < s.length(); i++){
for(int j = 0; j < i; j++){
if(dp[j] && wordSet.find(s.substr(j, i - j)) != wordSet.end())
dp[i] = true;
}
}
return dp[s.lenfth()];
}