找零钱问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/TuringGo/article/details/83020528

问题描述

有一张 100 元钞票,现在需要将其兑换成面额小于100的零钱,可供兑换的小面额的硬币有 1元、5元、10元、50元,为了携带方便,最终兑换出的硬币数量不超过15枚,求满足这样要求的组合有几种?

分析

  • 采用循环,可以穷举出每种硬币的可能性组合,然后判断每种组合是否同时满足硬币面额之和为100,且总数量不超过15枚的条件即可。
  • 这里有一个陷阱,那就是对于1元硬币和5元硬币而言,若是没有数量限制,最多可以兑换100枚1元硬币和20枚1元硬币。但是题中要求硬币数量最多15枚,因此,这两种硬币的数量最多15枚。

算法

  • 共有4种面额的硬币,使用4层嵌套的循环,穷举出硬币的组合
  • 只要同时满足硬币面额总和为100,且硬币数量不超过15,就记录一次

Python代码实现

count = 0
# 50元硬币最多2枚
for i in range(3):
    # 10元硬币最多10枚
    for j in range(11):
        # 5元硬币最多15枚
        for m in range(16):
            # 1元硬币最多15枚
            for n in range(16):
                if 50*i + 10*j + 5*m + 1*n == 100 and i+j+m+n < 16:
                    count += 1
print(count)
                    
# 20

递归算法求解

对于零钱兑换问题,常规思路就像上面的代码的思路一样:用零钱的各种组合来拼凑出最终的总金额。其实,用逆向思维反过来看,可以用总金额不断地去减去零钱的金额,直到恰好完全将总金额减完,此时消耗的各种零钱的不同个数,就是能够拼凑出总金额的零钱组合!

同时,考虑到程序的拓展性,应当使程序可以针对不同的金额找不同规定数量的零钱,还有当零钱种类增加或减少时,程序也能根据当前零钱种类,计算出正确的组合次数。

分析

  • 对于最终符合要求的零钱组合,我们可以发现其每种零钱的个数,其实是不确定的。例如:1元硬币的个数永远不可能是1个,因为如果只有1个一元硬币,那么剩下的50元、10元、5元硬币,无论怎么组合都不可能使最后结果是100。
  • 正因为每种硬币的个数是不确定的,所以我们需要穷举每种可能性,直到确定某种硬币的个数,然后剩余的硬币能够存在一种组合满足,最后恰好减完100。例如:- 例如:当我们输入的硬币组合是[1, 5, 10, 50]时,使用下面的代码时,第一层递归是对 50 的可能性数量循环(0、1、 2,三种数量可能性);第二层递归是对 10 的可能性数量循环(0、1、2、… 、10,十一种可能性);第三层递归是对 5 的可能性循环,虽然循环次数是 20 超过了 15 ,但最后超出范围的数字是不符合递归终止条件的,因此不会错误计算,只有 0 ~ 15 种可能;最后一层不会继续向下递归,直接判断剩余硬币金额除以 1 元硬币的数量,是否在当前剩余硬币数量范围之内。
  • 所以,对于组合(0,5,10,0)而言,意味着:当第一层递归 50 的可能性为 0 的时候;当第二层递归 10 的可能性为 5 的时候;第三层递归 5 的可能性为 10 的时候;最后一层递归的当前剩余金额 target 为 0,剩余可用硬币数量 num 为 0 ,target 除以 1 为 0 表示需要 0 个 1 元硬币,0 个硬币符合当前可用剩余硬币数量为 0 的范围。所以这个组合成立,计数一次!
  • 递归的过程是:只要当前硬币的组合可能性没有试验完,就继续试验下去;递归的终止条件是:剩余的硬币组合满足条件,好比:总硬币数量正好为15枚,或者硬币数量小于15枚

算法

  • 取出数组中最后一位的元素,然后将最后一位元素从原数组中删除
  • 如果当前数组已经没有元素了,那么意味着这一层递归中,已经取到了最后一个元素 1 。对于由 1 组成的零钱组合,只需要用当前剩余的金额除以 1 ,就可以得到1元零钱需要多少枚,然后将结果与当前可用的零钱的数量作比较,若没有超过当前可用的零钱数量,就意味着该组合是成功的,将count加一
  • 如果没有取到数组的最后一位,就需要继续向下递归,同时向下一层递归函数的传递的参数。
  • 对于目标金额(target)需要减去这一层所消耗的金额;对于下一层的零钱种类数组,需要减去最后一位,由于在一开始就用了pop()函数减去了,所以这里不需要再减去一个元素,这里的陷阱是不能直接将coins对象作为参数向下传递,而是需要调用copy()方法,得到一个它的复制匿名对象作为一个参数向下传递,否则递归中的每层coins对象会互相干扰!对于零钱数量,每层都会消耗若干个,这取决于当前循环是哪个值,因此向下一层传递的剩余零钱数量,是num - i

Python代码实现

def change(target, coins, num):
    global count
    # 获取coins最后一个元素,同时将其从原来数组中删除
    coin = coins.pop()
    if not coins:
        # 需要1元硬币金额的数量不能超过剩余硬币数量
        if target // coin <= num:
            count += 1
    else:
        # n = target // coin + 1 if target // coin < 16 else 16 
        # 上面一步表达式可有可无,因为递归终止时会判断数量是否符合规定数量
        for i in range(target // coin + 1):
            # 一定要调用copy函数,创建一个新的匿名对象,否则每层递归中的coins对象会互相干扰
            change(target - i * coin, coins.copy(), num - i)
            
count = 0
change(100, [1, 5, 10, 50], 15)
print(count)
count = 0
change(200, [5, 10, 50], 10)  #改变总金额、零钱种类、零钱数量,依然能够计算出结果
print(count)

# 20
# 4

当然了,找零钱问题,其实不仅可以用递归算法实现,还可以用贪婪算法实现。

想想,本题的递归算法都挺难的,所以还是下一次单独分析一下贪婪算法如何实现找零钱问题吧。

猜你喜欢

转载自blog.csdn.net/TuringGo/article/details/83020528
今日推荐