1. 程式人生 > >動態規劃系列之九找零錢

動態規劃系列之九找零錢

![](https://img2020.cnblogs.com/blog/1060878/202103/1060878-20210316230514983-494127254.jpg) # 問題 給定不同面額的硬幣 `coins` 和一個總金額 `amount`。編寫一個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1。 你可以認為每種硬幣的數量是無限的。 ``` coins = [1,2,5] amount = 11 結果:3,硬幣為:5,5,1 ``` # 解決過程 ## 解題思路 動態規劃解題思路是:`將大的問題拆解成小一點問題,小問題和大問題的解決思路是類似的` 給定一個總金額11,有三種硬幣:1,2,5。 將問題的規模減少:湊11難湊,就湊10,如果10難湊就湊9,一直到湊1,湊0。 ## 建立數學模型 新增陣列dp,表示湊到某一個數值的最小硬幣數。如dp[1]就代表金額為1的最少硬幣數,dp[10]就代表金額為10的最少硬幣數。該dp陣列長度為12,從金額為0到11,初始化為: ``` [12,12,12,12,12,12,12,12,12,12,12,12] ``` 之所以初始化為12,是總金額+1,因為可能會存在湊不到這個數的情況。當湊不到時,dp[-1]=12,湊得到時,即使硬幣金額最小為1,也只用11即可。 ## 狀態轉移方程 當要湊成的金額為0時: ``` dp = [0,12,12,12,12,12,12,12,12,12,12,12] ``` `金額為1時`: 由於硬幣有 1、2、5,所以,金額大於硬幣1的數額,所以一塊硬幣價值為1即可 ``` dp = [0,1,12,12,12,12,12,12,12,12,12,12] ``` `金額為2時`: 金額為2是,金額大於硬幣1,硬幣2,所以有兩種方案可以湊齊。 1、某一個金額加上硬幣2,那麼就是金額0 + 硬幣2 dp[0] = 0,所以dp[2] = 1 2、某一個金額加上硬幣1,那麼就是金額1 + 硬幣1 dp[1] = 1,所以dp[2] = dp[1] + 1 = 2 選擇最小的,所以dp[2] = 1 ``` dp = [0,1,1,12,12,12,12,12,12,12,12,12] ``` `金額為3時`: 金額大於硬幣1,硬幣2,所以有兩種方案 某一個金額加上硬幣2,就是 金額1 + 硬幣2 dp[3-2] + 1。dp[3-2],意思就是金額3減去硬幣2,得到的金額1其最小的組成硬幣數。dp[3] = dp[3-1] + 1 = 2 ![](https://img2020.cnblogs.com/blog/1060878/202103/1060878-20210316223812903-1794681331.png) 某一個金額加上硬幣1,就是 金額2 + 硬幣1 dp[3-1] + 2。dp[3-1],意思就是 金額3 - 硬幣1,得到的金額其最小組成的硬幣數。dp[3] = dp[3-2] + 1 = 2 所以,金額3時,dp[3] = 2 ``` dp = [0,1,1,2,12,12,12,12,12,12,12,12] ``` `金額為4時`: 金額大於硬幣1,硬幣2,所以有兩種方案 金額為2 + 硬幣2,即 dp[4-2] + 1,dp[4] = 2 金額為3 + 硬幣1,即 dp[4-1] + 1,dp[4] = 3 所以,金額為4時,dp[4] = 2 ``` dp = [0,1,1,2,2,12,12,12,12,12,12,12] ``` `金額為5時`: 金額大於硬幣1,硬幣2,硬幣5,所以有三種方案 金額為4 + 硬幣1,即 dp[5-1] + 1,dp[5] = 2 + 1 = 3 金額為3 + 硬幣2,即 dp[5-2] + 1,dp[5] = 2 + 1 = 3 金額為0 + 硬幣5,即 dp[5-5] + 1,dp[5] = 1 所以,dp[5] = 1 ``` dp = [0,1,1,2,2,5,12,12,12,12,12,12] ``` 最終按照這個規律,算出dp所有的數值。 # 程式碼 ``` 示例 1: 輸入:coins = [1, 2, 5], amount = 11 輸出:3 解釋:11 = 5 + 5 + 1 ``` ```python def coinChange(coins, amount): # 構建dp動態陣列 dp = [amount + 1] * (amount + 1) # 初始化 dp[0] = 0 for i in range(1, amount + 1): # 每一個金額,所有能湊成的方案的硬幣數,最後取最小值 temp = [dp[i]] for coin in coins: # 當金額大於某一個硬幣時才考慮,否則一定無法用大額硬幣湊成小額 if i >= coin: temp.append(dp[i-coin]+1) dp[i] = min(temp) print(dp) return -1 if dp[-1] == amount + 1 else dp[-1] coins = [1, 2, 5] amount = 11 res = coinChange(coins, amount) print(res) ``` ![](https://img2020.cnblogs.com/blog/1060878/202012/1060878-20201206143446538-389976919.png) # 小結 動態規劃系列到這一篇就算完結了,個人感覺動態規劃是演算法中很有難度,有技巧,有魅力的一種演算法。為了學明白這一種演算法,我也很多次在深夜裡思考求解。當我覺得自己似乎掌握了淺顯的規律之後,就把它記錄下來。這當然是我個人不太成熟的思想,動態規劃的深奧遠不是我能用9篇文章說明白的。但是,希望讀者能從這9篇文章學到一點我的經驗,摸到解決動態規劃問題的門檻。最後用我最喜歡的宮崎駿的動漫大集合作為本篇的