這裏找了3中方法實現,計算出找零錢方案,也可以計算出最少張數;多少種組合方法沒有考慮;希望有興趣的看看疏漏之處,在此也想拋磚引玉,希望找出該類問題經典解法。
#!/usr/bin/env python3
""" 找零錢問題
# 對於現實生活中的找零問題,假設有數目不限,面值爲2,5,10的硬幣, 求出找零方案。
#
# 對於此類問題,貪心算法可以找到近似最優解;
# 動態規劃可以找到最優解;其中也可以根據具體情況做一些優化。
#
# moneyValues = [2,5,10]
# moneyCount = [i, j, k] #示例 [30,40,50]
# price = N #示例 95
# Testtcases:(從面值大的幣種查找的用例)
>>> change([2,5,10],[30,40,50],95)
95 可找零爲 9 張10元,1 張5元,0 張2元
>>> change([2,5,10],[30,40,50],94)
94 可找零爲 9 張10元,0 張5元,2 張2元
>>> change([2,5,10],[30,40,50],90)
90 可找零爲 9 張10元,0 張5元,0 張2元
>>> change([2,5,10],[30,40,50],109)
109 可找零爲 10 張10元,1 張5元,2 張2元
# 判斷幣種組合
>>> change([2,5,10],[30,40,50],93)
93 不能找零
>>> change([2,5,10],[30,40,50],91)
91 不能找零
# 判斷數量限制
>>> change([2,5,10],[5,40,5],90)
90 可找零爲 5 張10元,8 張5元,0 張2元
>>> change([2,5,10],[30,0,50],95)
95 不能找零
>>> change([2,5,10],[30,40,1],94)
94 可找零爲 1 張10元,16 張5元,2 張2元
"""
def change(moneyValues, moneyCount, priceN):
moneyCounts = dict(zip(moneyValues, moneyCount))
if not assert_kinds_enough(moneyValues, priceN):
return display(None, priceN)
rst = get_DP_set_from_max(moneyCounts, priceN)
# rst = get_DP_set_from_min(moneyCounts, priceN)
# rst = get_greedy_set(moneyValues, priceN)
return display(rst, priceN)
def get_DP_set_from_max(moneyCounts, priceN):
# 規劃 i,j,k 的“最優”組合,算法複雜度主要在此
# 動態規劃
# 儘量使用大額面值,這樣張數最少,且少了一層循環
# 時間複雜度爲 面值種類數M,空間複雜度爲O(M),結果的 rst 用的一個有限 dict
moneyCounts = dict(sorted(moneyCounts.items(), key=lambda x:x[0], reverse=True))
rst = {value:0 for value, count in moneyCounts.items()}
if sum(value * count for value, count in moneyCounts.items()) < priceN:
return None
for value, count in moneyCounts.items():
if priceN <= 0:
break
tmpCount = priceN // value
if count > 0 or count >= tmpCount:
if count <= tmpCount:
rst[value] += count
else:
rst[value] += tmpCount
priceN -= rst[value] * value
else:
continue
if priceN > 0:
return None
else:
return rst
def get_DP_set_from_min(moneyCounts, priceN):
# 規劃 i,j,k 的“最優”組合,算法複雜度主要在此
# 動態規劃
# 儘量使用小額面值,這樣張數最多,在張數限制下確定是否有解
# 時間複雜度 O(MN),M爲面值種類數,N爲給定的找零的錢數;空間複雜度爲O(1),結果的 rst 用的一個有限 dict
moneyCounts = sorted(moneyCounts.items(), key=lambda x:x[0], reverse=False)
rst = {moneyValue:0 for moneyValue, count in moneyCounts}
if sum(moneyValue * count for moneyValue, count in moneyCounts) < priceN:
return None
for i in range(0,len(moneyCounts)):
iCount = 0
if moneyCounts[i][1] <= 0:
continue
for j in range(0, moneyCounts[i][1]+1):
if i > (len(moneyCounts)-2):
break
if (priceN - moneyCounts[i][0]*j) % moneyCounts[i+1][0] == 0:
iCount = j
if iCount*moneyCounts[i][0] >= priceN:
break
if iCount != 0:
rst[moneyCounts[i][0]] += iCount
priceN -= iCount * moneyCounts[i][0]
elif moneyCounts[i][1] == 0:
continue
else:
rst[moneyCounts[i][0]] += priceN // moneyCounts[i][0]
priceN -= rst[moneyCounts[i][0]] * moneyCounts[i][0]
if priceN <= 0:
break
if priceN > 0:
return None
return rst
def get_greedy_set(moneyValues, priceN):
# 規劃 i,j,k 的“最優”組合:貪心算法
# 時間複雜度 O(MN),M爲面值種類數,N爲給定的找零的錢數;空間複雜度 O(1),至多用到2個確定大小的 dict
# 儘量使用大額面值,不考慮幣種數量限制
moneyValues = sorted(moneyValues, reverse=True)
rst = {moneyValue:0 for moneyValue in moneyValues}
for moneyValue in moneyValues:
while priceN >= moneyValue and priceN > 0:
priceN -= moneyValue
rst[moneyValue] += 1
return rst
def assert_kinds_enough(moneyValues, price):
# 面值中不包含1, 所以定有一部分 N 不能夠組合,如1, 3, 等
if price % 5 == 0:
return True
elif price % 2 == 0:
return True
elif (price % 5 != 0) and ((price % 5) % 2 == 0):
return True
else:
return None
def display(rst, price):
if rst:
print(f"{price} 可找零爲 {rst[10]} 張10元,{rst[5]} 張5元,{rst[2]} 張2元")
else:
print(f"{price} 不能找零")
if __name__ == "__main__":
import doctest
doctest.testmod()