硬幣劃分問題指:給定一些基礎硬幣(如1、2、5元),最少需要多少枚硬幣能夠劃分能夠湊成目標的金額。
思路一:遞歸法
從大——>小進行逐層分解,遞歸求得所有的劃分可能性,然後取得其中數量最少的劃分方案。
# 這裏只給出所有可能的劃分方案,剩下的求元素數量或最優方案需做後處理
# 按照這裏給出方案,每次分解硬幣的順序也是區分的
def split_coins(total: int, coins: list):
if total in coins:
yield (total, )
else:
if total > min(coins):
for c in coins:
for p in split_coins(total-c, coins):
yield p + (c, )
results = list(split_coins(10, [2, 4]))
for i in results:
print(i)
# (4, 2, 2, 2)
# (2, 4, 2, 2)
# (4, 4, 2)
# (4, 2, 4)
# (2, 4, 4)
思路二:動態規劃
按照從小——>大的順序,分別給出當前目標金額下的所需最小硬幣數量。注意:因爲基礎硬幣設置的不合理,目標金額可能不存在劃分方案,需要單獨標識。
子狀態:表示目標金額爲時的所需最小硬幣數量
狀態轉移:,其中表示基礎硬幣中的硬幣金額
注意邊界條件和迭代條件:
(1)時, ,表示不存在分配方案
(2)時, ,表示最優方案爲1枚硬幣(即對應的基礎硬幣)
(3)在進行狀態轉移時,若,則不應該參與後續計算
下面給出具體代碼:
def MinCoins(total: int, coins: list):
m = [-1 for _ in range(total+1)] # 子狀態: 從[0-目標金額]序列下的所有對應金額的最少所需硬幣
for i in range(1, total+1):
if i < min(coins): # 邊界條件1:金額過小,不存在分配方案
m[i] = -1
elif i in coins: # 邊界條件2:金額=某個最小硬幣,分配方案爲1
m[i] = 1
else:
candidates = []
for c in coins:
if i - c > 0 and m[i-c] > 0: # 因爲python中切片操作的限制,所以必須加以第一個條件 i - c > 0;第二個條件表示,前序存在分解方案
candidates.append(m[i-c])
if len(candidates) > 0:
m[i] = min(candidates) + 1
else:
m[i] = -1
return m[-1]
print(MinCoins(10, [1, 2, 5])) # 2
print(MinCoins(10, [1, 2, 5, 10])) # 1
print(MinCoins(13, [1, 2, 5])) # 4
print(MinCoins(11, [2, 4])) # -1
思考:若不光要給出最少所需的硬幣數,還需要給出具體的劃分方案呢?
解決方案:添加輔助變量,記錄動態規劃過程中的具體方案細節
def MinCoins2(total: int, coins: list):
"""
除了只給出最少所需的硬幣數,還給出對應的方案
"""
m = [-1 for _ in range(total+1)]
s = [[] for _ in range(total+1)]
for i in range(1, total+1):
if i < min(coins):
m[i] = -1
elif i in coins:
m[i] = 1
s[i] = [i]
else:
candidates = [] # 記錄每個子問題所需要的最少硬幣數
candidate_index = [] # 記錄每個子問題對應最優方案時,前一步方案對應的索引編號
candidate_coin = [] # 記錄每個子問題對應最優方案時,相較於前一步方案所需加入的這枚硬幣的值
for c in coins:
if i - c > 0 and m[i-c] > 0:
candidates.append(m[i-c])
candidate_index.append(i-c)
candidate_coin.append(c)
if len(candidates) > 0:
min_candidate = min(candidates)
min_candidate_index = candidates.index(min_candidate)
m[i] = min_candidate + 1
s[i] = s[candidate_index[min_candidate_index]] + [candidate_coin[min_candidate_index]]
else:
m[i] = -1
return m[-1], s[-1]
number, detail = MinCoins2(10, [1, 2, 5])
print(number) # 2
print(detail) # [5, 5]
number, detail = MinCoins2(10, [1, 2, 5, 10])
print(number) # 1
print(detail) # [10]
number, detail = MinCoins2(13, [1, 2, 5])
print(number) # 4
print(detail) # [5, 5, 2, 1]
number, detail = MinCoins2(11, [2,4])
print(number) # -1
print(detail) # []