算法—专题—找零钱问题【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()


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