您的位置:首页 > 科技 > 能源 > 【代码随想录训练营第42期 Day37打卡 - 动态规划Part5 - 完全背包问题

【代码随想录训练营第42期 Day37打卡 - 动态规划Part5 - 完全背包问题

2024/9/25 5:07:13 来源:https://blog.csdn.net/2303_79786049/article/details/141432472  浏览:    关键词:【代码随想录训练营第42期 Day37打卡 - 动态规划Part5 - 完全背包问题

目录

一、做题心得

二、完全背包模板

模板题: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. 1 阶 + 2 阶
  3. 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背包的打卡练习,今天完全背包的问题也变得容易理解。理解了模板的由来之后,今天的打卡基本上就是模板的套用。最后,今天的打卡到此结束,后边将会继续加油!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com