完全背包
52. 携带研究材料(第七期模拟笔试) (kamacoder.com)
完全背包和01背包的区别在于物品的使用次数,01背包中所有物品只能使用1次,而完全背包中能够使用无限次,01背包使用逆序遍历背包,使得每个物品只能使用一次,考虑以下博客。
动态规划——完全背包问题(公式推导,组合、排列)-CSDN博客
(先遍历物品,再遍历背包,一维DP数组每次取值要用到前一轮的结果,所以使用逆序),完全背包使用正序遍历,此外,针对纯完全背包类问题(放进背包中物品的最大价值),遍历背包和物品的顺序不固定,都可以。
#include <iostream>
#include <vector>
using namespace std;
void test_CompletePack(vector<int> weight, vector<int> value, int bagweight) {
// 初始化动态规划数组dp,大小为bagweight + 1,初值都为0
// dp[j]表示背包容量为j时能够达到的最大价值
vector<int> dp(bagweight + 1, 0);
// 遍历所有可能的背包容量
for (int j = 0; j <= bagweight; j++) {
// 遍历所有物品
for (int i = 0; i < weight.size(); i++) {
// 如果当前物品的重量不超过背包容量,则尝试放入物品
if (j - weight[i] >= 0) {
// 更新dp[j],选择当前物品和不选择当前物品,取两种情况的最大值
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
}
// 输出背包容量为bagweight时能够达到的最大价值
cout << dp[bagweight] << endl;
}
int main() {
int N, V; // N表示物品数量,V表示背包容量
cin >> N >> V;
vector<int> weight; // 存储物品的重量
vector<int> value; // 存储物品的价值
// 输入物品的重量和价值
for (int i = 0; i < N; i++) {
int w, v;
cin >> w >> v;
weight.push_back(w);
value.push_back(v);
}
test_CompletePack(weight, value, V);
return 0;
}
算法的时间复杂度为O(n*m),空间复杂度为O(n)。
零钱兑换II
每种零钱数值能使用无限次,以组合形成最终要求的结果amount(amount可理解为一个容量为amount的背包)本题为完全背包问题。本题和之前的目标和题目类似。
和目标和类似,dp数组中dp[j]表示装满容量为j的背包共有dp[j]种方法
最终返回的值为dp[amount]。
dp[j] += dp[j-coins[i]],dp[0] = 1(便于遍历),其余元素全初始为0;
此时考虑遍历顺序,完全背包问题对遍历物品和背包的顺序没有要求,但对完全背包问题中求排列和组合就有要求了。
如果考虑组合,需要先遍历物品,再遍历背包。如本题假定coins = [1,2,5],amount = 5,则在遍历物品后,只会从小到大依次加入,如[1,2,2],而不会出现[2,1,2]这样的情况(当第一个元素放入2之后,不会回头寻找先前的元素,即实现了组合),而先遍历背包(则在amount固定的情况下,会从头到尾遍历coins数组,数组中的所有元素在每次加入时都有机会选择,这里也就是排列问题)再遍历物品就会出现上述的情况。
for(int i = 0; i < coins.size(); i ++){
for(int j = coins[i];j <= amount; j++){
}
}//先遍历物品,再遍历背包
for(int j = 0; i <=amount; j ++){
for(int i = 0;i<coins.size(); i++){
}
}//先遍历背包,再遍历物品
返回dp[amount]。具体代码如下
class Solution {
public:
int change(int amount, vector<int>& coins) {
// 初始化动态规划数组dp,大小为amount+1,初值都为0
// dp[j]表示组成金额为j的所有可能组合数
vector<int> dp(amount + 1, 0);
// 金额为0的组合数为1,即不使用任何硬币的情况
dp[0] = 1;
// 遍历所有可能的硬币面额
for (int i = 0; i < coins.size(); i++) {
// 遍历所有可能的金额
for (int j = coins[i]; j <= amount; j++) {
// 更新dp[j],考虑使用当前硬币和不使用当前硬币的情况
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
};
算法的时间复杂度为O(m*n),空间复杂度为O(n)。
组合总和IV
本题就是一个排列题,基本的dp数组和上题一样,由于强调元素顺序(排列),所以选择先背包,后物品的遍历顺序。返回的也是dp[target]。
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
// 初始化动态规划数组dp,大小为target+1,初值都为0
// dp[j]表示达到金额为j的组合数
vector<uint> dp(target + 1, 0);
// 金额为0的组合数为1,即不使用任何数字的情况
dp[0] = 1;
// 遍历所有可能的金额
for (int j = 0; j <= target; j++) {
// 遍历所有可能的数字
for (int i = 0; i < nums.size(); i++) {
// 如果当前金额减去当前数字的结果非负,并且dp[j]还有增加的空间
if (j - nums[i] >= 0) {
// 更新dp[j],考虑使用当前数字和不使用当前数字的情况
dp[j] += dp[j - nums[i]];
}
}
}
// 返回达到金额为target的组合数,即dp[target]
return dp[target];
}
};
算法的时间复杂度为O(m*n),空间复杂度为O(m),m为target值,n为nums.size()。
爬楼梯(进阶)
假设你正在爬楼梯,需要n阶才能到达楼顶,每次至多可爬m(1<=m<n)个台阶,有多少种不同的方法可以爬到楼顶。57. 爬楼梯(第八期模拟笔试) (kamacoder.com)
首先这是个排列题,此外,爬梯m个台阶的数目可以为无穷,如果台阶数无穷的话。和上题类似,背包问题,求爬的方法的数目。
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 定义两个变量n和m,分别用于存储输入的整数
int n, m;
// 读取n和m的值
cin >> n >> m;
// 创建一个动态规划数组dp,大小为n+1,初值都为0
vector<int> dp(n + 1);
// 初始化dp[0]为1,因为0可以由0个1和m组成
dp[0] = 1;
// 遍历所有可能的整数
for (int j = 1; j <= n; j++) {
// 遍历所有可能的1和m的组合
for (int i = 1; i <= m; i++) {
// 如果当前整数j减去当前组合i(1或m)的结果非负
if (j - i >= 0) {
// 更新dp[j],考虑使用当前组合和不使用当前组合的情况
dp[j] += dp[j - i];
}
}
}
// 输出dp[n],即n可以由多少个1和m组成的数表示
cout << dp[n] << endl;
return 0;
}
时间复杂度和空间复杂度同上。