目录
一、做题心得
二、完全背包模板
模板题:AcWing 3. 完全背包问题
题目链接
模板1:最大价值问题
模板2:组合排列数问题
组合数
排列数
三、题目与题解
题目一:518. 零钱兑换 II
题目链接
题解:动态规划--完全背包
题目二:377. 组合总和 Ⅳ
题目链接
题解:动态规划--完全背包
题目三:57. 爬楼梯(卡码网)
题目链接
题解:动态规划--完全背包
三、小结
一、做题心得
今天是代码随想录打卡的第37天,来到了动态规划章节的完全背包问题。完全背包问题相较于前边打卡的01背包问题,由一个物品只能使用一次,变成了一个物品可以使用多次。那么代码上自然也会存在相应改动。不过由于都属于背包问题的范畴,思路上讲是一致的。因此,有了前边打卡的基础,今天的内容就比较简单了。
话不多说,直接开始今天的内容。
二、完全背包模板
模板题:AcWing 3. 完全背包问题
题目链接
3. 完全背包问题 - AcWing题库
模板1:最大价值问题
#include<bits/stdc++.h>
using namespace std;const int N = 1005;
int n, m;
int v[N];
int w[N];
int dp[N] = {0}; //也可以vector数组signed main() {cin>>n>>m;for (int i = 0; i < n; i++) {cin>>v[i]>>w[i];}for (int i = 0; i < n; i++) { //遍历物品for (int j = 0; j <= m; j++) { //注意:正序遍历背包(从0到最大容量)--表示物品可以重复使用--注意与01背包区别if (j >= v[i]) { //当前背包能装下该物品时(注意是大于等于)dp[j] = max(dp[j], dp[j - v[i]] + w[i]);}}}cout<<dp[m];return 0;
}
其实完全背包和01背包问题的主要区别就在于:在遍历背包上,01背包采用逆序遍历,而完全背包采用顺序(正序)遍历--这是因为逆序遍历确定了每个物品只能选一次,而顺序遍历时每个物品可以选择多次。还有一个区别就是01背包只能先遍历物品再遍历背包,而完全背包两种顺序都可以。
还有一道模板题和这个一致:52. 携带研究材料(第七期模拟笔试) (kamacoder.com)
附一份代码:
#include<bits/stdc++.h>
using namespace std;const int N = 10005;
int n, m;
int v[N];
int w[N];
int dp[N] = {0};signed main() {cin>>n>>m;for (int i = 0; i < n; i++) {cin>>v[i]>>w[i];}for (int i = 0; i < n; i++) {for (int j = 0; j <= m; j++) {if (j >= v[i]) {dp[j] = max(dp[j], dp[j - v[i]] + w[i]);}}}cout<<dp[m];return 0;
}
当然,这个模板是为了解决完全背包问题对于求得最大价值这一类问题,后边还会提到解决求得方案数(组合数或者排列数)这一类完全背包问题。
模板2:组合排列数问题
组合数:同一组合,不同顺序,一种情况
排列数:同一组合,不同顺序,不同情况
组合数
先遍历物品,再遍历背包
for (int i = 0; i < n; i++) { //组合数--先遍历物品,再遍历背包for (int j = 0; j <= m; j++) {if (j >= v[i]) { //注意:>=dp[j] += dp[j - v[i]]; }}}
排列数
先遍历背包,再遍历物品
for (int i = 0; i <= m; i++) { //排列数--先遍历背包,再遍历物品for (int j = 0; j < n; j++) {if (i >= v[j]) { //注意:>=dp[i] += dp[i - v[j]]; }}}
三、题目与题解
题目一:518. 零钱兑换 II
题目链接
518. 零钱兑换 II - 力扣(LeetCode)
给你一个整数数组
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
题解:动态规划--完全背包
这道题一看到每一种面额的硬币有无限个,就可以联想到这是一个完全背包的组合数问题。
题目要求得出可以凑成总金额的硬币组合数,其实就是刚刚模板2中的组合数问题。(不讲求顺序)在这里物品就是硬币,dp[j]表示凑成金额总数为j的组合数。
这里需要注意的就是初始化 dp[0] = 1。
代码如下:
class Solution {
public:int change(int amount, vector<int>& coins) {/* 完全背包问题--一维dp */vector<int> dp(5001, 0); //dp[j]:凑成金额总数为j的组合数dp[0] = 1; //注意:初始化dp[0] = 1(后续中,若j == coins[i],选择当前硬币,就出现dp[0],若dp[0] == 0,组合数就没有增加)for (int i = 0; i < coins.size(); i++) { //组合数--先遍历物品,再遍历背包for (int j = 0; j <= amount; j++) {if (j >= coins[i]) { //注意:>=dp[j] += dp[j - coins[i]]; }}}return dp[amount];}
};
题目二:377. 组合总和 Ⅳ
题目链接
377. 组合总和 Ⅳ - 力扣(LeetCode)
给你一个由 不同 整数组成的数组
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
题解:动态规划--完全背包
这道题就是一个典型的完全背包的排列数问题,即每个组合的情况是需要讲求顺序的。
在这里需要注意的就是需要防溢出:为了防止dp[i] + dp[i - nums[j]] 过大溢出,我们需要保证 dp[i - nums[j]] < INT_MAX - dp[i]。
其余的思路上就跟上一题一致,只是排列数问题需要先遍历背包,再遍历物品。
代码如下:
class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(1005, 0);dp[0] = 1; //注意求方案数的背包问题:初始化dp[0] = 1for (int i = 0; i <= target; i++) { //排列数--先遍历背包再遍历物品for (int j = 0; j < nums.size(); j++) {if (i >= nums[j] && dp[i - nums[j]] < INT_MAX - dp[i]) { //题目数据保证答案符合 32 位整数范围:dp[i - nums[j]] < INT_MAX - dp[i]在这里用作防溢出dp[i] += dp[i - nums[j]];}}}return dp[target];}
};
题目三:57. 爬楼梯(卡码网)
题目链接
57. 爬楼梯(第八期模拟笔试) (kamacoder.com)
题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
输入描述
输入共一行,包含两个正整数,分别表示n, m
输出描述
输出一个整数,表示爬到楼顶的方法数。
输入示例
3 2
输出示例
3
提示信息
数据范围:
1 <= m < n <= 32;当 m = 2,n = 3 时,n = 3 这表示一共有三个台阶,m = 2 代表你每次可以爬一个台阶或者两个台阶。
此时你有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶段
- 1 阶 + 2 阶
- 2 阶 + 1 阶
题解:动态规划--完全背包
这题也比较简单,很容易想到完全背包的做法--完全背包的排列数问题(即需要讲求顺序)。
物品在这就是指你能爬台阶的步数(1--m相当于各个物品的体积),背包就是台阶。dp[i]表示台阶数为 i 时爬到楼顶的方案数(其实就是排列数)。
理清题意之后,这里也是直接套用模板2的排列数问题。
代码如下:
#include<bits/stdc++.h>
using namespace std;int n, m;
vector<int> dp(35, 0);signed main() {cin>>n>>m;dp[0] = 1;for(int i = 0; i <= n; i++) {for (int j = 1; j <= m; j++) {if (i >= j) {dp[i] += dp[i - j];}}}cout<<dp[n];return 0;
}
三、小结
有了前边01背包的打卡练习,今天完全背包的问题也变得容易理解。理解了模板的由来之后,今天的打卡基本上就是模板的套用。最后,今天的打卡到此结束,后边将会继续加油!