題目
硬幣。給定數量不限的硬幣,幣值爲25分、10分、5分和1分,編寫代碼計算n分有幾種表示法。(結果可能會很大,你需要將結果模上1000000007)
示例1:
輸入: n = 5
輸出:2
解釋: 有兩種方式可以湊成總金額:
5=5
5=1+1+1+1+1
示例2:
輸入: n = 10
輸出:4
解釋: 有四種方式可以湊成總金額:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1
說明:
注意:
你可以假設:
- 0 <= n (總金額) <= 1000000
解題思路
1)01揹包問題
有N件物品和一個容量爲V的揹包。第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使價值總和最大。
基本思路:
這是最基礎的揹包問題,特點是:每種物品僅有一件,可以選擇放或不放。令f[i][v]表示前i件物品恰放入一個容量爲v的揹包可以獲得的最大價值。若只考慮第i件物品的策略(放或不放),那麼就可以轉化爲一個只牽扯前i-1件物品的問題。如果不放第i件物品,那麼問題就轉化爲“前i-1件物品放入容量爲v的揹包中”,價值爲f[i-1][v];如果放第i件物品,那麼問題就轉化爲“前i-1件物品放入剩下的容量爲v-c[i]的揹包中”,此時能獲得的最大價值就是f[i-1][v-c[i]]再加上通過放入第i件物品獲得的價值w[i]。
則其狀態轉移方程爲:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
優化空間複雜度至O(V):
for i=1..N
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
注意是以v=V…0的順序推f[v],這樣才能保證推f[v]時f[v-c[i]]保存的是狀態f[i-1][v-c[i]]的值。
2)完全揹包問題
有N種物品和一個容量爲V的揹包,每種物品都有無限件可用。第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。其特點是每個物品可以選無數次。
基本思路:
這個問題非常類似於01揹包問題,所不同的是每種物品有無限件。也就是從每種物品的角度考慮,與它相關的策略已並非取或不取兩種,而是有取0件、取1件、取2件……等很多種。如果仍然按照解01揹包時的思路,令f[i][v]表示前i種物品恰放入一個容量爲v的揹包的最大價值。仍然可以按照每種物品不同的策略寫出狀態轉移方程:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i] | 0<=k*c[i]<=v}
01揹包問題要按照v=V…0的逆序來循環,這是因爲要保證第i次循環中的狀態f[i][v]是由狀態f[i-1][v-c[i]]遞推而來。換句話說,這正是爲了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時,依據的是一個絕無已經選入第i件物品的子結果f[i-1][v-c[i]]。而現在完全揹包的特點恰是每種物品可選無限件,所以在考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已選入第i種物品的子結果f[i][v-c[i]],所以就可以並且必須採用v=0…V的順序循環。
因此,基本思路中的狀態轉移方程可以等價地變形成這種形式:
f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}
將這個方程用一維數組實現,便得到了如下的僞代碼:
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-c[i]]+w[i]}
3)對於本題來說,由於硬幣個數不限,所以是一個完全揹包問題。具體地,我們設置一個 dp 數組來求解
3.1)二維情況:
dp[i][j] 使用前i種硬幣計算j分的表示法種數,令coins=[25, 10, 5, 1]
dp[i][j] = dp[i-1][j] + dp[i-1][j-coins[i]] + dp[i-1][j-2*coins[i]] + ... dp[i-1][j-k*coins[i]],其中j>=k*coins[i];
dp[i][j-coins[i]] = dp[i-1][j-coins[i]] + dp[i-1][j-2*coins[i]] + dp[i-1][j-k*coins[i]],其中j>=k*coins[i];
因此,
dp[i][j] = dp[i-1][j] + dp[i][j-coins[i]]
3.2)優化到一維:
dp[k] 表示組成k面額的硬幣情況數。
dp[k] = dp[k] + dp[k-coins[i]]
代碼
class Solution:
def waysToChange(self, n: int) -> int:
dp = [0] *(n+1)
dp[0] = 1
coins = [25, 10, 5, 1]
for i in coins:
for j in range(i, n+1):
dp[j] = dp[j] + dp[j-i]
return dp[-1]%1000000007