LeetCode 322 零錢兌換

2021-10-03 17:22:01 字數 2699 閱讀 4138

**leetcode, 題目位址

給定不同面額的硬幣 coins 和乙個總金額 amount。編寫乙個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1。

每一種硬幣都有選擇和不選兩種情況,因此最簡單的方法就是窮舉所有可能性,從中找到最小的選擇,遞迴樹如下:

狀態樹

每次節點的子節點都會出現3個分支,因此時間複雜度是指數級。

儘管明知這個**是不能通過leetcode,我還是把**給寫完了。

class solution1 

void dfs(int depth, int amount, vector& coins)

for (auto coin : coins)

return ;}};

對於一些比較正常的資料,這個**都能正常執行,但是對於leetcode中的[186,419,83,408] 6249,標準答案是20,也就是至少有20層,因此計算量至少是3^20,但是遞迴深度可以達到75層,計算量就是乙個天文數字了。

遞迴樹其實是一種深度優先的搜尋方法,我們可以採用廣度優先搜尋方法,對遞迴樹按層進行遍歷,第一次遇到amount=0的時候,也就是最小的遍歷層數。

class solution2 );

while ( !q.empty()) );}}

return -1;}};

儘管看起來, bfs或許比dfs能夠更快的找到最終的解(不需要遍歷所有的狀態),但是由於遞迴樹本身就是指數增長,因此只要層數過大,時間就會驚人的增長。

你可以保持[1,2,5]不變,然後依次測試,10,20,50,80,100. 你會發現前4個解決速度都比較快,但是到了100,時間就上天了。

暴力遞迴的問題在於,節點的增長是指數級別的。如果我們每次都選擇當前的最優解,也就是對於11,從1,2,5中找交換的硬幣的時候,都以5,2,1這種順序進行選擇,就避免了指數級別的增長。

class solution 

int dfs(int level, int amount, vector& coins)

for (auto coin : coins)

return -1;}};

但是貪心演算法有乙個弊端,你得要證明能夠從區域性最優一直推導出全域性最優解,才能保證你的結果是正確的。如果無法保證,那麼貪心演算法不一定保證最終結果就是最優解,所以這裡貪心演算法也是無法通過。

在我們之前的遞迴樹中,無論採用何種遍歷方式(dfs或bfs),都無法逃脫時間指數級別增長的命運。

不過當我們仔細觀察遞迴樹的時候,我們會發現一些節點是被重複計算的。如果能避免這些重複計算,那麼時間複雜度就會降低到o(n^2)

在原來遞迴的**上加上記憶化,我寫了很久,主要是不知道在遞迴的時候,如何記錄當前情況下,如何挑選最優子問題。

先放上正確的答案

class solution 

int dfs(vector& coins, int amount)

}dict[amount] = (min == int_max ? -1 : min);

return dict[amount];}};

遞迴返回的從最後一層到當前層的所需的步數。而之前的遞迴程**則是每次記錄當前的深度,最終拿深度和全域性最小值進行比較。

下面則是我的錯誤示範。我的**主題也是想算出最後位置到當前位置的經歷的步數,但是我想用最後一層的深度減去當前的深度。結果每乙個amount對應都是1.

// 遞迴+記憶化

class solution4

int dfs(int depth, int amount, vector& coins)

if (dict.find(amount) != dict.end() )

int local_min = int_max;

for (auto coin : coins)

dict[amount] = (local_min == int_max ? -1 : local_min);

return dict[amount];}};

事實上遞迴加記憶化就是一種動態規劃,只不過遞迴是一種自頂向下的策略。並且有了之前遞迴加記憶化的經驗,我們寫遞推就變得簡單了。

第一步: 定義子問題。我們求解組成金額為n的最少硬幣數,也就是求解一系列 n-k (k=coins) 子問題中的最小選擇加1。以amount=11,coins=1,2,5為例。求解amount=11的最少硬幣,也就是在10,9,6這三種選擇中挑選其中所需硬幣最小的子問題,然後加1.

第二步:定義dp陣列. f(n) = min + 1

第三步: 定義dp方程: dp[i] = min(dp[i], dp[i-coins[j] ] )+ 1

最終**如下

class solution }}

return dp[amount] > amount ? -1 : dp[amount];}};

**中的細節,

最終來看,自底向上的動態遞迴的**反而是最簡潔的。

leetcode322 零錢兌換

給定不同面額的硬幣 coins 和乙個總金額 amount。編寫乙個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 1。示例 1 輸入 coins 1,2,5 amount 11輸出 3解釋 11 5 5 1 示例 2 輸入 coins 2 amount 3...

leetcode 322 零錢兌換

給定不同面額的硬幣 coins 和乙個總金額 amount。編寫乙個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 1。示例 1 輸入 coins 1,2,5 amount 11 輸出 3 解釋 11 5 5 1示例 2 輸入 coins 2 amount ...

LeetCode 322 零錢兌換

322 零錢兌換 題目 給定不同面額的硬幣 coins 和乙個總金額 amount。編寫乙個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 1。示例 1 輸入 coins 1,2,5 amount 11 輸出 3 解釋 11 5 5 1 示例 2 輸入 co...