Python数据结构20:动态规划:找零兑换问题的动态规划解法并显示使用的硬币组合

在我们使用递归算法时,可能会出现规模庞大的重复计算,用一个中间表记录每个计算过的最优解法,就可以避免大量的重复计算。中间结果记录可以很好解决找零兑换问题。实际上,这种方法还不能称为动态规划,而是叫做“memoization(记忆化/函数值缓存)”的技术提高了递归解法的性能。

1. 找零兑换的动态规划解法

动态规划算法采用了一种更有条理的方式来得到问题的解。
找零兑换的动态规划算法从最简单的“1分钱找零”的最优解开始,逐步递加上去,直到我们需要的找零钱数。
在找零递加的过程中,设法保持每一分钱的递加都是最优解,一直加到求解找零钱数,自然得到最优解。
递加的过程能保持最优解的关键是,其依赖于更少钱数最优解的简单计算,而更少钱数的最优解已经得到了。
问题的最优解包含了更小规模子问题的最优解,这是一个最优化问题能够用动态规划策略解决的必要条件。
originalamount找零兑换问题具体来说就是:

numCoins 就是当前需要找零的硬币个数。
1 5 10 25就是硬币的币值。
originalamount就是要找零的钱数。

例如用动态规划解决11分钱的兑换问题。

  1. 从1分钱兑换开始,逐步建立一个兑换表。
看这个表的顺序就是从上往下。

兑换5分钱的时候,最优解就是一个5分的硬币。

  1. 计算11分钱的兑换法,我们做如下几步:
  • 首先减去1分硬币,剩下10分钱查表最优解是1,共需要2个硬币
  • 然后减去5分硬币,剩下6分钱查表最优解是2,共需要3个硬币
  • 最后减去10分硬币,剩下1分钱查表最优解是1,共需要2个硬币

25分就不减了,因为明显 11 < 24,不可能用25分来找零。

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

解决思路如下:

need_change = 65
init_min_coins = [0] * (need_change + 1)  # 建一个空表
real_value_list = [1, 5, 10, 25]


def dynamic_programming(change, min_coins, value_list):
    for current_change in range(1, change + 1):
        min_coins[current_change] = current_change  # 最坏的情况是只用一分钱兑换
        for value in value_list:  # 遍历每个币值
            if current_change >= value:
                if 1 + min_coins[current_change - value] < min_coins[current_change]:
                    # 若使用当前币值时,需要的最小硬币个数最小,就更新最小硬币数表中的个数
                    min_coins[current_change] = 1 + min_coins[current_change - value]
    return min_coins[change]  # 直接查表,最后一个就是我们想要的结果


print(dynamic_programming(need_change, init_min_coins, real_value_list))

我们注意到动态规划算法的dynamic_programming并不是递归函数。虽然这个问题是从递归算法开始解决,但最终我们得到一个更有条理的高效非递归算法。

动态规划中最主要的思想是:

  • 最简单情况开始到达所需找零的循环
  • 其每一步都依靠以前的最优解来得到本步骤的最优解,直到得到答案

2. 找零兑换:动态规划算法扩展

前面的算法已经得到了最少硬币的数量,但没有返回硬币如何组合。
扩展算法的思路很简单,只需要在生成最
优解列表同时跟踪记录所选择的那个硬币币值即可。
在得到最后的解后,减去选择的硬币币值,回溯到表格之前的部分找零,就能逐步得到每一步所选择的硬币币值。

need_change = 63
init_min_coins = [0] * (need_change + 1)  # 建一个空表
init_coins_used = [0] * (need_change + 1)  # 建一个空表用于保存每一步新增的硬币币值
real_value_list = [1, 5, 10, 21, 25]


def dynamic_programming(change, min_coins, value_list, coins_used):
    for current_change in range(1, change + 1):
        min_coins[current_change] = current_change  # 最坏的情况是只用一分钱兑换
        for value in value_list:  # 遍历每个币值
            if current_change >= value:
                if 1 + min_coins[current_change - value] <= min_coins[current_change]:
                    # 若使用当前币值时,需要的最小硬币个数最小,就更新最小硬币数表中的个数
                    min_coins[current_change] = 1 + min_coins[current_change - value]
                    coins_used[current_change] = value  # 当前使用的币值存入到表中
    return min_coins[change], coins_used  # 直接查表,最后一个就是我们想要的结果


def combination_values(change, coins_used):
    print(coins_used[change])
    while change > 1:
        # 每次显示并减去当前新增的硬币,就是回溯了使用的所有币值,即显示了每次新增的硬币的币值
        if coins_used[change - coins_used[change]] > 0:
            print(coins_used[change - coins_used[change]])
        change = change - coins_used[change]


min_coins, coins_used = dynamic_programming(need_change, init_min_coins, real_value_list, init_coins_used)
print(min_coins)
print(coins_used)
combination_values(need_change, coins_used)

3
[0, 1, 1, 1, 1, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 21, 21, 21, 21, 25, 25, 25, 25, 25, 25, 21, 21, 21, 21, 25, 25, 25, 25, 25, 25, 21, 21, 21, 21, 25, 25, 25, 25, 25, 25, 25, 21, 21, 21, 25, 25, 25, 25, 25, 25, 25, 21, 21]
21
21
21

参考

本文的知识来源于B站视频 【慕课+课堂实录】数据结构与算法Python版-北京大学-陈斌-字幕校对-【完结!】,是对陈斌老师课程的复习总结
代码是我自己写的,功能比较简单

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值