動態規劃法求解硬幣劃分問題

硬幣劃分問題指:給定一些基礎硬幣(如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)

思路二:動態規劃
按照從小——>大的順序,分別給出當前目標金額下的所需最小硬幣數量。注意:因爲基礎硬幣設置的不合理,目標金額可能不存在劃分方案,需要單獨標識。

子狀態m[i]m[i]表示目標金額爲ii時的所需最小硬幣數量

狀態轉移m[i]=minciCm[ici]+1m[i]=\min \limits_{c_i\in C}m[i-c_i]+1,其中cic_i表示基礎硬幣中的硬幣金額

注意邊界條件和迭代條件:
(1)i<mincii<\min c_i時,m[i]=1m[i]=-1 ,表示不存在分配方案
(2)iCi \in C時,m[i]=1m[i]=1 ,表示最優方案爲1枚硬幣(即對應的基礎硬幣cic_i
(3)在進行m[i]=minciCm[ici]+1m[i]=\min \limits_{c_i\in C}m[i-c_i]+1狀態轉移時,若m[ici]=1m[i-c_i]=-1,則不應該參與後續計算

下面給出具體代碼:

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)   # []
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章