問題
給定不同面額的硬幣 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
某一個金額加上硬幣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
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)
小結
動態規劃系列到這一篇就算完結了,個人感覺動態規劃是演算法中很有難度,有技巧,有魅力的一種演算法。為了學明白這一種演算法,我也很多次在深夜裡思考求解。當我覺得自己似乎掌握了淺顯的規律之後,就把它記錄下來。這當然是我個人不太成熟的思想,動態規劃的深奧遠不是我能用9篇文章說明白的。但是,希望讀者能從這9篇文章學到一點我的經驗,摸到解決動態規劃問題的門檻。最後用我最喜歡的宮崎駿的動漫大集合作為本篇的封面。