「程式碼隨想錄」本週學習小結!(動態規劃系列五)
技術標籤:leecode題解java演算法資料結構動態規劃面試
相信很多小夥伴刷題的時候面對力扣上近兩千道題目,感覺無從下手,我花費半年時間整理了Github專案:leetcode刷題攻略。 裡面有100多道經典演算法題目刷題順序、配有40w字的詳細圖解,常用演算法模板總結,以及難點視訊講解,按照list一道一道刷就可以了!star支援一波吧!
週一
動態規劃:377. 組合總和 Ⅳ中給定一個由正整陣列成且不存在重複數字的陣列,找出和為給定目標正整數的組合的個數(順序不同的序列被視作不同的組合)。
題目面試雖然是組合,但又強調順序不同的序列被視作不同的組合,其實這道題目求的是排列數!
遞迴公式:dp[i] += dp[i - nums[j]];
這個和前上週講的組合問題又不一樣,關鍵就體現在遍歷順序上!
在動態規劃:518.零錢兌換II 中就已經講過了。
如果求組合數就是外層for迴圈遍歷物品,內層for遍歷揹包。
如果求排列數就是外層for遍歷揹包,內層for迴圈遍歷物品。
如果把遍歷nums(物品)放在外迴圈,遍歷target的作為內迴圈的話,舉一個例子:計算dp[4]的時候,結果集只有 {1,3} 這樣的集合,不會有{3,1}這樣的集合,因為nums遍歷放在外層,3只能出現在1後面!
所以本題遍歷順序最終遍歷順序:target(揹包)放在外迴圈,將nums(物品)放在內迴圈,內迴圈從前到後遍歷。
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. 爬樓梯進階版(完全揹包)中我們進階了一下。
改為:每次可以爬 1 、 2、…、m 個臺階。問有多少種不同的方法可以爬到樓頂呢?
1階,2階,… m階就是物品,樓頂就是揹包。
每一階可以重複使用,例如跳了1階,還可以繼續跳1階。
問跳到樓頂有幾種方法其實就是問裝滿揹包有幾種方法。
此時大家應該發現這就是一個完全揹包問題了!
和昨天的題目動態規劃:377. 組合總和 Ⅳ基本就是一道題了,遍歷順序也是一樣一樣的!
程式碼如下:
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.零錢兌換給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函式來計算可以湊成總金額所需的最少的硬幣個數(每種硬幣的數量是無限的)。
這裡我們都知道這是完全揹包。
遞迴公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
關鍵看遍歷順序。
本題求錢幣最小個數,那麼錢幣有順序和沒有順序都可以,都不影響錢幣的最小個數。。
所以本題並不強調集合是組合還是排列。
那麼本題的兩個for迴圈的關係是:外層for迴圈遍歷物品,內層for遍歷揹包或者外層for遍歷揹包,內層for迴圈遍歷物品都是可以的!
外層for迴圈遍歷物品,內層for遍歷揹包:
// 版本一
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迴圈遍歷物品:
// 版本二
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.完全平方數給定正整數 n,找到若干個完全平方數(比如 1, 4, 9, 16, …)使得它們的和等於 n。你需要讓組成和的完全平方數的個數最少(平方數可以重複使用)。
如果按順序把前面的文章都看了,這道題目就是簡單題了。 dp[i]的定義,遞推公式,初始化,遍歷順序,都是和動態規劃:322. 零錢兌換 一樣一樣的。
要是沒有前面的基礎上來做這道題,那這道題目就有點難度了。
這也體現了刷題順序的重要性。
先遍歷揹包,在遍歷物品:
// 版本一
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];
}
};
先遍歷物品,在遍歷揹包:
// 版本二
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
求排列數:動態規劃:377. 組合總和 Ⅳ、動態規劃:70. 爬樓梯進階版(完全揹包)
求最小數:動態規劃:322. 零錢兌換、動態規劃:279.完全平方數
此時我們就已經把完全揹包的遍歷順序研究的透透的了!
就醬,學演算法,認準「程式碼隨想錄」,你會發現相見恨晚!
我是程式設計師Carl,可以找我組隊刷題,也可以在B站上找到我,關注公眾號程式碼隨想錄來和上萬錄友一起打卡學習演算法,來看看,你會發現相見恨晚!
如果感覺對你有幫助,不要吝嗇給一個