打卡第43天,去广州浪了几天,补卡补卡。
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
例子:
背包最大重量为4。
物品为:
物品 | 重量 | 价值 |
---|---|---|
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
每件商品都有无限个!
问背包能背的物品最大价值是多少?
#include
using namespace std;
int main() {
int N, V;
scanf("%d%d", &N, &V);
vector<int> v(N);
vector<int> w(N);
for(int i = 0; i < N; i++) {
scanf("%d %d", &v[i], &w[i]);
}
vector<vector<int>> dp(N, vector<int> (V + 1, 0));
for(int i = v[0]; i <= V; i++) dp[0][i] = dp[0][i - v[0]] + w[0];
for(int i = 1; i < N; i++)
for(int j = 1; j <= V; j++) {
if(v[i] > j) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]), dp[i][j - v[i]] + w[i]);
}
// for(int i = 0 ; i < N; i++) {
// for(int j = 0; j <= V; j++) printf("%d ", dp[i][j]);
// printf("\n");
// }
printf("%d", dp[N - 1][V]);
return 0;
}
遍历顺序
我们知道01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。
而完全背包的物品是可以添加多次的,所以要从小到大去遍历
在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!
因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。
遍历物品在外层循环,遍历背包容量在内层循环,
一维数组
#include
using namespace std;
int main() {
int N, V;
scanf("%d%d", &N, &V);
vector<int> v(N);
vector<int> w(N);
for(int i = 0; i < N; i++) {
scanf("%d %d", &v[i], &w[i]);
}
vector<int> dp(V + 1, 0);
for(int i = 0; i < N; i++)
for(int j = v[i]; j <= V; j++) {
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
printf("%d", dp[V]);
return 0;
}
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
示例 1:
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入:amount = 10, coins = [10]
输出:1
提示:
1 <= coins.length <= 300
1 <= coins[i] <= 5000
coins
中的所有值 互不相同0 <= amount <= 5000
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp(amount + 1, 0); //dp[j] 金额为j时候有多少种方式凑成
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];
}
};
虽然解法一样,但我明显没有想那么多。
遍历顺序get
先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。
for (int i = 0; i < coins.size(); i++) { // 遍历物品
for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
dp[j] += dp[j - coins[i]];
}
}
假设:coins[0] = 1,coins[1] = 5。
那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。
所以这种遍历顺序中dp[j]里计算的是组合数!
如果把两个for交换顺序,代码如下:
for (int j = 0; j <= amount; j++) { // 遍历背包容量
for (int i = 0; i < coins.size(); i++) { // 遍历物品
if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
}
}
背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。
给你一个由 不同 整数组成的数组 nums
,和一个目标整数 target
。请你从 nums
中找出并返回总和为 target
的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
示例 2:
输入:nums = [9], target = 3
输出:0
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 1000
nums
中的所有元素 互不相同1 <= target <= 1000
进阶: 如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?
直接想起上一道题,先遍历物品再遍历背包是排列,先遍历背包再遍历物品是组合。
但是注意测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]。
本题也可以用回溯暴搜,如果需要把排列都列出来,回溯可以解
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target + 1, 0);
dp[0] = 1;
for(int j = 0; j <= target; j++) {
for(int i = 0; i < nums.size(); i++) {
if(j >= nums[i] && dp[j] < INT_MAX - dp[j - nums[i]]) dp[j] += dp[j - nums[i]];
}
}
return dp[target];
}
};