【每天一道算法题9】【Python】找零钱问题

题目描述

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例:
coins = [1, 2, 5]
amount = 100
返回 20

coins = [1, 2, 5]
amount = 103
返回 22

解法一:动态规划(自底向上)

分析:
假设 coins = [coin1, coin2, coin3],amount = N
我们要计算的是,用coins里面的硬币来组成N,并且硬币数最少。
我们可以这么思考,假设有一个硬币池,里面有无数个coin1, coin2, coin3,我们不断地从里面挑硬币出来,直到总金额达到N。那被挑出来的这些硬币就是加起来等于N的一个组合,当然不同的挑法,肯定有不同的组合,我们要做的就是在这些组合里面找到最少的硬币个数就行了。
这里我们再逆向思考一下,在每个符合规定(相加总金额等于N)的组合里面,最后挑出来的硬币肯定是coin1,coin2,coin3其中之一。假设f(N)表示组成N的最少硬币数,那么就有如下关系:
f(N) = min(f(N-coin1), f(N-coin2), f(N-coin3)) + 1
这个公式在动态规划里面叫做状态转移方程,其实很好理解,我们计算f(N), 实际上可以转化为计算f(N-coin1)、f(N-coin2)、f(N-coin3),然后取它们的最小值,再加1(因为我减去了一个硬币)。
那自底向上的思想就是,我们要计算f(N),那我们依次计算出f(1)、f(2)、f(3)…f(N-coin1)、f(N-coin2)、f(N-coin3),这样我们就可以轻松的得到f(N)了。
代码实现:

def solution(coins: list, amount: int) -> int:
    # 定义一个数组,用于存储组成0-amount每一个面额的最小硬币数,初始化为无限大
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    for coin in coins:  # 遍历硬币面额
        # 遍历每一个总金额,为了保证x - coin >= 0,我们从大于coin开始遍历
        for x in range(coin, amount + 1):
            # 以下代码是核心
            dp[x] = min(dp[x], dp[x - coin] + 1)
    return dp[amount] if dp[amount] != float('inf') else -1

解法二:递归

递归的解法,主要是要找到递进关系和结束递归的条件.

  1. 递进关系 f(N) = min(f(N-coin1), f(N-coin2), f(N-coin3)…) + 1
  2. 结束递归的条件,N <= 0

代码实现:

import functools

def solution(coins: list, amount: int) -> int:
    @functools.lru_cache(amount)   # 把函数调用结果记录到缓存,记录次数为amount
    def dp(_amount):
        if _amount < 0: 
            return -1
        if _amount == 0: 
            return 0
        mini = int(1e9)
        for coin in coins: 
            # 进入递归,计算f(N-coin1)、 f(N-coin2)、f(N-coin3)...     
            res = dp(_amount - coin)
            if 0 <= res < mini:
                mini = res + 1
        return mini if mini < int(1e9) else -1
    if amount < 1: 
        return 0
    return dp(amount)
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值