目录
518. 零钱兑换 II
-
文章讲解:代码随想录
动态规划法
-
完全背包:01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。而完全背包的物品是可以添加多次的,所以要从小到大去遍历
-
解题思路
但本题和纯完全背包不一样,纯完全背包是凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数!注意题目描述中是凑成总金额的硬币组合数,组合不强调元素之间的顺序,排列强调元素之间的顺序。
-
解题步骤
-
确定dp数组以及下标的含义:dp[j]凑成总金额j的货币组合数为dp[j]
-
dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。所以递推公式:dp[j] += dp[j - coins[i]];
-
dp数组如何初始化:首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。
-
确定遍历顺序:本题中我们是外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)。这种遍历顺序中dp[j]里计算的是组合数。如果把两个for交换顺序,此时dp[j]里算出来的就是排列数。
-
举例推导dp数组:
-
-
代码一:动态规划
// 时间复杂度: O(mn),其中 m 是amount,n 是 coins 的长度
// 空间复杂度: O(m)
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> 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. 组合总和 Ⅳ
-
文章讲解:代码随想录
动态规划法
-
解题思路
-
本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列!
-
-
解题步骤
-
确定dp数组以及下标的含义:dp[i]: 凑成目标正整数为i的排列个数为dp[i]
-
确定递推公式:dp[i](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j]) 推导出来。因为只要得到nums[j],排列个数dp[i - nums[j]],就是dp[i]的一部分。求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];
-
个数可以不限使用,说明这是一个完全背包。得到的集合是排列,说明需要考虑元素之间的顺序。所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
-
举例来推导dp数组:
-
-
代码一:动态规划
// 时间复杂度: O(target * n),其中 n 为 nums 的长度
// 空间复杂度: O(target)
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> 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];
}
};
70. 爬楼梯 (进阶)
-
题目链接:57. 爬楼梯(第八期模拟笔试)
-
文章讲解:代码随想录
动态规划法
-
解题思路
一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?这又有难度了,这其实是一个完全背包问题。1阶,2阶,.... m阶就是物品,楼顶就是背包。每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。问跳到楼顶有几种方法其实就是问装满背包有几种方法。
-
解题步骤
-
确定dp数组以及下标的含义:dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。
-
dp数组如何初始化:既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。下标非0的dp[i]初始化为0,因为dp[i]是靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果。
-
确定遍历顺序:这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样!所以需将target放在外循环,将nums放在内循环。每一步可以走多次,这是完全背包,内循环需要从前向后遍历。
-
-
代码一:动态规划
// 时间复杂度: O(n * m)
// 空间复杂度: O(n)
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
while (cin >> n >> m) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) { // 遍历背包
for (int j = 1; j <= m; j++) { // 遍历物品
if (i - j >= 0) dp[i] += dp[i - j];
}
}
cout << dp[n] << endl;
}
}