0
点赞
收藏
分享

微信扫一扫

《dp补卡——完全背包问题》

《dp补卡——完全背包问题》_完全平方数
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​​

这一题01背包中的组合问题有相似之处。只需要把遍历顺序修改就行了。
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]);
}
}
//如果没有被赋值,那么说明没有解,返回-1
if(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()];
}
};


举报

相关推荐

0 条评论