Python数据结构与算法35:找零兑换问题的动态规划解法

:本文如涉及到代码,均经过Python 3.7实际运行检验,保证其严谨性。

本文阅读时间约为10分钟

什么是动态规划?

上一节用中间结果的记录来解决递归算法的问题——极度低效,但这种方法还不能称为动态规划,而是叫作memorization(记忆化/函数值缓存),这种技术大大提高了递归解法的性能,甚至能降低递归解法的时间复杂度。

什么是动态规划解法?

动态规划算法采用了一种更有条理的方式来得到问题的解。

拿找零兑换的问题来说,动态规划算法从最简单的一分钱找零的最优解开始,逐步递加上去,直到我们需要的找零钱数。

在找零递加的过程中,设法保持每一分钱的递加都是最优解,一直加到求解找零钱数,自然得到最优解。

在递加的过程当中,能保持最优解的关键是,其依赖于更少钱数最优解的简单计算,而更少钱数的最优解在前面的课程中已经得到了。这就是动态规划的解法。

问题的最优解包含了更小规模子问题的最优解,这是一个最优化问题能够用动态规划策略解决的必要条件

采用动态规划来解决11分钱的兑换问题

从1分钱兑换开始,一直到11分钱,逐步建立一个兑换表,如下图所示。

Pic-411-1 动态规划解决11分钱找零的兑换表

计算11分钱的兑换法,按硬币面值枚举:

  • 减去1分硬币,剩下10分钱查表最优解是1。这种方法得到的结果是1+1=2个硬币。
  • 减去5分硬币,剩下6分钱查表最优解是2。这种方法得到的结果是1+2=3个硬币。
  • 减去10分硬币,剩下1分钱查表,最优解是1。这种方法得到的结果是1+1=2个硬币。
  • 因为11分钱不足25分,所以减去25分面值的枚举不考虑在内。

通过上述最小值得到最优解:2个硬币。

Pic-411-2 动态规划解决11分钱找零的兑换枚举图示

按上面的思路,代码就不难写出来:

# 找零兑换:动态规划算法v1。
def dpMakeChange(coinValueList, change, minCoins):
    # 从1分钱开始到change逐个计算最小硬币数。
    for cents in range(1, change + 1):
        # 1. 初始化一个最大值。
        coinCount = cents
        # 2. 减去每个硬币,向后查最少硬币数,同时记录总的最少数。
        for j in [c for c in coinValueList if c <= cents]:
            if minCoins[cents - j] + 1 < coinCount:
                coinCount = minCoins[cents - j] + 1
        # 3. 得到当前最少硬币数,记录到列表中。
        minCoins[cents] = coinCount
    # 返回最后一个结果。
    return minCoins[change]  # 循环结束,得到最优解。

print(dpMakeChange([1, 5, 10, 21, 25], 63, [0] * 64))

<<<3
找零兑换问题动态规划算法的扩展

上面的动态规划算法代码中,函数dpMakeChange并不是递归函数。这是因为,这个问题是从递归算法开始的,但最终在尝试过程中,我们发现了一条更好更高效的算法,而这种算法并非递归算法。

动态规划中最主要的思想是:从最简单情况开始到达所需找零的循环,其每一步都依靠以前的最优解来得到本步骤的最优解,直到得到答案。

上面的找零兑换:动态规划算法v1已经得到了最小硬币的数量,但是没有返回硬币如何组合。扩展算法就是解决这个问题,其思路是在生成最优解列表的同时,跟踪记录所选择的那个硬币币值即可。

在得到最后的解之后,减去选择的硬币币值,回溯到表格之前的部分找零,就能逐步得到每一步所选择的硬币币值。

扩展后的动态规划算法的代码如下:

# 找零兑换:动态规划算法v2。
def dpMakeChange(coinValueList, change, minCoins, coinsUsed):
    # 从1分钱开始到change逐个计算最小硬币数。
    for cents in range(1, change + 1):
        # 1. 初始化一个最大值。
        coinCount = cents
        newCoin = 1  # 初始化一下新加的硬币。
        # 2. 减去每个硬币,向后查最少硬币数,同时记录总的最少数。
        for j in [c for c in coinValueList if c <= cents]:
            if minCoins[cents - j] + 1 < coinCount:
                coinCount = minCoins[cents - j] + 1
                newCoin = j  # 对应最小数量,所减的硬币。
        # 3. 得到当前最少硬币数,记录到列表中。
        minCoins[cents] = coinCount
        coinsUsed[cents] = newCoin  
    # 返回最后一个结果。
    return minCoins[change]  # 循环结束,得到最优解。

def printCoins(coinsUsed, change):
    coin = change
    while coin > 0:
        thisCoin = coinsUsed[coin]
        print(thisCoin)
        coin = coin - thisCoin

amnt = 63
clist = [1, 5, 10, 21, 25]
coinsUsed = [0] * (amnt + 1)
coinCount = [0] * (amnt + 1)

print("Making change for", amnt, "requires")
print(dpMakeChange(clist, amnt, coinCount, coinsUsed), "coins")
print("They are:")
printCoins(coinsUsed, amnt)
print("The used list is as follows:")
print(coinsUsed)

<<<
Making change for 63 requires
3 coins
They are:
21
21
21
The used list is as follows:
[0, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 21, 1, 1, 1, 25, 1, 1, 1, 1, 5, 10, 1, 1, 1, 10, 1, 1, 1, 1, 5, 10, 21, 1, 1, 10, 21, 1, 1, 1, 25, 1, 10, 1, 1, 5, 10, 1, 1, 1, 10, 1, 10, 21]
<<<

To be continued.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容