204 lines
7.2 KiB
Markdown
204 lines
7.2 KiB
Markdown
# 本周小结!(动态规划系列五)
|
||
|
||
## 周一
|
||
|
||
[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)中给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数(顺序不同的序列被视作不同的组合)。
|
||
|
||
题目面试虽然是组合,但又强调顺序不同的序列被视作不同的组合,其实这道题目求的是排列数!
|
||
|
||
递归公式:dp[i] += dp[i - nums[j]];
|
||
|
||
这个和前上周讲的组合问题又不一样,关键就体现在遍历顺序上!
|
||
|
||
在[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) 中就已经讲过了。
|
||
|
||
**如果求组合数就是外层for循环遍历物品,内层for遍历背包**。
|
||
|
||
**如果求排列数就是外层for遍历背包,内层for循环遍历物品**。
|
||
|
||
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!
|
||
|
||
所以本题遍历顺序最终遍历顺序:**target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历**。
|
||
|
||
```CPP
|
||
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. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA)中我们进阶了一下。
|
||
|
||
改为:每次可以爬 1 、 2、.....、m 个台阶。问有多少种不同的方法可以爬到楼顶呢?
|
||
|
||
1阶,2阶,.... m阶就是物品,楼顶就是背包。
|
||
|
||
每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。
|
||
|
||
问跳到楼顶有几种方法其实就是问装满背包有几种方法。
|
||
|
||
**此时大家应该发现这就是一个完全背包问题了!**
|
||
|
||
|
||
和昨天的题目[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)基本就是一道题了,遍历顺序也是一样一样的!
|
||
|
||
代码如下:
|
||
```CPP
|
||
class Solution {
|
||
public:
|
||
int climbStairs(int n) {
|
||
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];
|
||
}
|
||
}
|
||
return dp[n];
|
||
}
|
||
};
|
||
|
||
```
|
||
|
||
代码中m表示最多可以爬m个台阶,代码中把m改成2就是本题70.爬楼梯可以AC的代码了。
|
||
|
||
## 周三
|
||
|
||
[动态规划:322.零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数(每种硬币的数量是无限的)。
|
||
|
||
这里我们都知道这是完全背包。
|
||
|
||
递归公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
|
||
|
||
关键看遍历顺序。
|
||
|
||
本题求钱币最小个数,**那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。**。
|
||
|
||
所以本题并不强调集合是组合还是排列。
|
||
|
||
**那么本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!**
|
||
|
||
|
||
外层for循环遍历物品,内层for遍历背包:
|
||
```CPP
|
||
// 版本一
|
||
class Solution {
|
||
public:
|
||
int coinChange(vector<int>& coins, int amount) {
|
||
vector<int> dp(amount + 1, INT_MAX);
|
||
dp[0] = 0;
|
||
for (int i = 0; i < coins.size(); i++) { // 遍历物品
|
||
for (int j = coins[i]; j <= amount; j++) { // 遍历背包
|
||
if (dp[j - coins[i]] != INT_MAX) { // 如果dp[j - coins[i]]是初始值则跳过
|
||
dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
|
||
}
|
||
}
|
||
}
|
||
if (dp[amount] == INT_MAX) return -1;
|
||
return dp[amount];
|
||
}
|
||
};
|
||
```
|
||
|
||
外层for遍历背包,内层for循环遍历物品:
|
||
|
||
```CPP
|
||
// 版本二
|
||
class Solution {
|
||
public:
|
||
int coinChange(vector<int>& coins, int amount) {
|
||
vector<int> dp(amount + 1, INT_MAX);
|
||
dp[0] = 0;
|
||
for (int i = 1; i <= amount; i++) { // 遍历背包
|
||
for (int j = 0; j < coins.size(); j++) { // 遍历物品
|
||
if (i - coins[j] >= 0 && dp[i - coins[j]] != INT_MAX ) {
|
||
dp[i] = min(dp[i - coins[j]] + 1, dp[i]);
|
||
}
|
||
}
|
||
}
|
||
if (dp[amount] == INT_MAX) return -1;
|
||
return dp[amount];
|
||
}
|
||
};
|
||
```
|
||
|
||
## 周四
|
||
|
||
[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ)给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少(平方数可以重复使用)。
|
||
|
||
|
||
如果按顺序把前面的文章都看了,这道题目就是简单题了。 dp[i]的定义,递推公式,初始化,遍历顺序,都是和[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ) 一样一样的。
|
||
|
||
要是没有前面的基础上来做这道题,那这道题目就有点难度了。
|
||
|
||
**这也体现了刷题顺序的重要性**。
|
||
|
||
先遍历背包,在遍历物品:
|
||
|
||
```CPP
|
||
// 版本一
|
||
class Solution {
|
||
public:
|
||
int numSquares(int n) {
|
||
vector<int> dp(n + 1, INT_MAX);
|
||
dp[0] = 0;
|
||
for (int i = 0; i <= n; i++) { // 遍历背包
|
||
for (int j = 1; j * j <= i; j++) { // 遍历物品
|
||
dp[i] = min(dp[i - j * j] + 1, dp[i]);
|
||
}
|
||
}
|
||
return dp[n];
|
||
}
|
||
};
|
||
```
|
||
|
||
先遍历物品,在遍历背包:
|
||
|
||
```CPP
|
||
// 版本二
|
||
class Solution {
|
||
public:
|
||
int numSquares(int n) {
|
||
vector<int> dp(n + 1, INT_MAX);
|
||
dp[0] = 0;
|
||
for (int i = 1; i * i <= n; i++) { // 遍历物品
|
||
for (int j = 1; j <= n; j++) { // 遍历背包
|
||
if (j - i * i >= 0) {
|
||
dp[j] = min(dp[j - i * i] + 1, dp[j]);
|
||
}
|
||
}
|
||
}
|
||
return dp[n];
|
||
}
|
||
};
|
||
```
|
||
|
||
|
||
## 总结
|
||
|
||
本周的主题其实就是背包问题中的遍历顺序!
|
||
|
||
我这里做一下总结:
|
||
|
||
求组合数:[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)
|
||
求排列数:[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA)
|
||
求最小数:[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)、[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ)
|
||
|
||
此时我们就已经把完全背包的遍历顺序研究的透透的了!
|
||
|
||
|
||
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>
|