算法—專題—找零錢問題【Python3】

這裏找了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()


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章