Python实现找零兑换的三种解法

找零兑换

找零兑换问题最直接的解法就是贪心策略
比如问题:有面值1、5、10、25的硬币,求解兑换63元所需的最少硬币数。

贪心策略的思想就是不断的利用面值最大的硬币去尝试,不行了,在尝试较小面值的硬币,该例中也即使用25的硬币去尝试,2枚25的硬币后,剩下13元,在面值为10的硬币1枚,最后用3枚1元的硬币,因此最少的硬币数为6枚。这种思想也类似于中学时天平实验,不断的用大点的砝码尝试使天平平衡。
但是贪心策略拥有明显的弊端,优先使用最大面值的硬币,未必能取得最好的结果,在该例中如果存在21元的面值,最优的解法应该是3枚21面值的硬币,而非6枚。

接下来,我们使用递归的思想解决找零兑换的问题。

一、递归解法

递归解法的核心思想:
递归思想其实类似于我们学单片机时的中断的概念,中断中最重要的三个环节是进入断点,执行中断子程序,以及断点恢复。不一样的地方在于递归调用的子程序是不断缩小规模的自身,因此,每次调用自身都会进入一段规模更小的一段子程序,执行完规模更小的子程序在返回规模稍大的上一级子程序,直到返回主程序,继续执行完主程序。这里的断点恢复,其实使用的就是的概念,每次进入断点后,将断点压入栈,断点恢复的时候从栈顶弹出断点。

因此,要想完成一个递归的操作,需要几个条件:
1、该问题存在规模更小的相同问题
2、规模最小的问题,存在合适的结束条件。否则不断调用子程序,始终无法返回
3、递归能够向最小规模的问题上演进

找零兑换思路:
1、大的找零问题,可以分解为更小的找零问题。
2、找零问题存在最小问题解,即硬币的最小面值,等于该面值返回硬币数1结束最小规模子程序
3、能够通过不断缩减规模使子程序向最小规模问题上演进

因此可以使用递归解决问题,最重要的问题是解决如何缩进规模。可以采用面值缩减法,如下操作【面值为1、5、10、25的情况】:
面值缩减法
程序如下:

def recMc(coinValueList, change):
    '''
    找零问题复杂版
    :param coinValueList: 面值列表
    :param change: 钱
    :return: 最少硬币数
    '''
    minCoins = change
    if change in coinValueList:
        return 1
    else:
        for i in [c for c in coinValueList if c <= change]:
            numCoins = 1 + recMc(coinValueList, change - i)
            if numCoins < minCoins:
                minCoins = numCoins

    return minCoins

这是最基本的递归方法,但是该方法缺点明显,重复计算的次数太多了,比如某个更小规模的最小硬币数已经计算出来了,但是在上级往下递归的过程中,还是在不停的往下调用,导致效率极低,在我的电脑上,该程序需要用18秒才能得到结果。

如何解决重复计算的问题呢?

二、加入记录器的递归方法

最简单的做法就是更小规模的硬币数计算出来后,用个列表进行记录下来,下次递归到该面值的时候直接查表就可以得到该规模问题的解,并返回,从而避免更深层的调用。

程序如下:

def recMc_pro(coinValueList, change, knownResults):
    '''
    找零问题改进版,使用一个列表存放子问题的解,避免重复的递归调用
    :param coinValueList: 面值列表
    :param change: 钱
    :return: 最少硬币数
    '''
    minCoins = change
    if change in coinValueList:
        knownResults[change] = 1  # 记录最优解
        return 1
    elif knownResults[change] > 0:
        return knownResults[change]  # 查表成功, 直接使用最优解
    else:
        for i in [c for c in coinValueList if c <= change]:
            numCoins = 1 + recMc_pro(coinValueList, change - i, knownResults)
            if numCoins < minCoins:
                minCoins = numCoins
                #  找到最优解, 记录到表中
                knownResults[change] = minCoins
    return minCoins

改进后的递归方法,极大的加快了程序的运行,仅需200多次递归即可返回最优解

三、动态规划解法

动态规划的思想:
不同于递归的方法,递归是从大问题分解成小问题,最后在从小问题逐步返回大问题的解。动态规划是从最小的问题出发,通过不断的解决小问题,直接干掉大问题。

例如找零兑换问题:假设还是面值表为[1, 5, 10, 25],需要兑换11元。
那么,从最小的问题开始,兑换1元情况下, 需要最少1枚1元的硬币,兑换2元需要2个1元硬币,同理兑换3元3个 ,兑换4元4个,到兑换5元的时候,可以最少用5元硬币一个,兑换6元最少用5元硬币1个,1元硬币1个…从而建立了一个找零兑换表,查阅该表即可得到任意的兑换结果。
兑换表
程序如下:

def dynamic(change_list, change):
    """
    动态规划的方法解决找零钱的问题
    :param change_list: 面值表,如本例[1, 5, 10, 21, 25]
    :param change: 钱
    :return: 兑换的零钱最小数量
    """
    # 创建一个记录表,记录表的长度为 change + 1
    record_list = [0] * (change + 1)
    for change in range(1, change+1):
        # 零钱数在表中,直接记录并返回硬币数1
        if change in change_list:
            record_list[change] = 1
        else:
            # 如果不在表中,查上一次的最少硬币数,减去上次的change值,再次查表,加和硬币数
            # 创建了一个缓存,避免只用cent = 1 去测试
            temp = []
            # 循环计算,要防止cent超过当前的change否则会出现表里查不到
            for cent in [cents for cents in change_list if cents <= change]:
                last_num = record_list[change - cent]
                temp.append(last_num + record_list[cent])
            record_list[change] = min(temp)
            # 清空缓存
            temp.clear()

    return record_list[change]

核心的思想,依然还是之前的公式,在这里引入了一个缓存的技巧,存放每个面值情况下对应的最优数量,并对当前change取最优的结果。总的来看就是维护记录表每个面值情况下的最优解,在计算后的结果时不断的查看表,怎么能加出更少的结果。

自我学习记录,参考资料陈斌教授的数据结构与分析python版教程,链接如下:

北京大学 数据结构与算法Python

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值