> 文章列表 > 代码随想录算法训练营第四十四天 | 完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

代码随想录算法训练营第四十四天 | 完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

代码随想录算法训练营第四十四天 | 完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

打卡第43天,去广州浪了几天,补卡补卡。

今日任务

  • 完全背包
    1. 零钱兑换 II
    1. 组合总和 Ⅳ

完全背包

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。

例子:

背包最大重量为4。

物品为:

物品 重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

每件商品都有无限个!

问背包能背的物品最大价值是多少?

  1. dp以及下标的定义
    dp[i][j] 背包重量为 j 最大价值。
  2. 递推公式
    dp[i][j]=max(max(dp[i−1][j],dp[i−1][j−v[i]]+w[i]),dp[i][j−v[i]]+w[i])dp[i][j] = max(max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]), dp[i][j - v[i]] + w[i])dp[i][j]=max(max(dp[i1][j],dp[i1][jv[i]]+w[i]),dp[i][jv[i]]+w[i])
  3. 初始化
    dp[i][0] = 0; 重量为0,没有价值
    dp[0][j]

我的题解

#include<bits/stdc++.h>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<bits/stdc++.h>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;
}

518. 零钱兑换 II

给你一个整数数组 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

我的题解

代码随想录算法训练营第四十四天 | 完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

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]里计算的是组合数!
代码随想录算法训练营第四十四天 | 完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ
如果把两个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}两种情况。

此时dp[j]里算出来的就是排列数!
代码随想录算法训练营第四十四天 | 完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 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];}
};