完全背包理论基础
视频链接:带你学透完全背包问题! 和 01背包有什么差别?遍历顺序上有什么讲究?_哔哩哔哩_bilibili
完全背包与01背包不同的地方就是:01背包每种物品只能取一次,而完全背包每种物品可以取无数次。
题目链接:518. 零钱兑换 II - 力扣(LeetCode)
视频链接:动态规划之完全背包,装满背包有多少种方法?组合与排列有讲究!| LeetCode:518.零钱兑换II_哔哩哔哩_bilibili
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 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中,{221}与{122}是一种组合而不是两种,所以本题求的是组合数(不强调元素之间的顺序)。本题是完全背包,因为物品是可以取无限次的。
依旧是动规五部曲
1.确定dp数组以及下标含义
dp[j]:凑成总金额j的货币组合数为dp[j]。
2.确定递推公式
dp[j] += dp[j - coins[i]](求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]],本题公式是根据这个公式推理的来的)。
3.dp数组初始化
dp[0] = 1。
4.确定遍历顺序
虽然说完全背包中,两层for循环是可以颠倒的,但是本题就不行,本题第一层for循环遍历钱(物品),第二层for循环遍历总额(背包),完全背包就不用特意强调内层循环必须要从后向前遍历。本题有顺序的原因就是因为我们要求的是组合数,一旦颠倒顺序就变成求排列数了。
5.打印dp数组
class Solution {
public:
int change(int amount, vector& coins) {
vector dp(amount + 1,0);
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];
}
};
题目链接:377. 组合总和 Ⅳ - 力扣(LeetCode)
视频链接:动态规划之完全背包,装满背包有几种方法?求排列数?| LeetCode:377.组合总和IV_哔哩哔哩_bilibili
给你一个由 不同 整数组成的数组 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
本题其实和上一道题差不多,唯一的区别就是本题是有序的,要求排列数,就是在遍历顺序上把两层for循环颠倒一下,其余的几乎不变。
依旧是动规五部曲
1.确定dp数组以及下标含义
dp[i]: 凑成目标正整数为i的排列个数为dp[i]。
2.确定递推公式
dp[i] += dp[i - nums[j]]。
3.dp数组初始化
dp[0] = 1。
4.确定遍历顺序
第一层for循环遍历背包,第二层for循环遍历物品。
5.打印dp数组
class Solution {
public:
int combinationSum4(vector& nums, int target) {
vector dp(target + 1,0);
dp[0] = 1;
for(int i = 0;i <= target;i++) {
for(int j = 0;j < nums.size();j++) {
if(i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
};