(面試題 08.11)硬幣

題目

硬幣。給定數量不限的硬幣,幣值爲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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章