天天刷Leetcode——322. 零钱兑换

问题描述

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

Leetcode 322 零钱兑换: https://leetcode-cn.com/problems/coin-change/.

解题思路

1. 初级动态规划——不带存储单元的递归

分析:该问题具有最优子结构。因为如果要求amount = n的最少硬币数(原问题),可以转化为amount = n-1(子问题)再加上1(一个一块的硬币),这就是原问题的答案。因为硬币的数量没有限制,子问题之间没有相互制约的,是独立的。这就是典型的动态规划问题。

状态转移方程的确认

  1. 状态:原问题和子问题中变化的变量,由于硬币数量无限,唯一变化的就是amount。
  2. 状态转移方程:对于amount,至少需要dp(n)个硬币凑成该金额。那么状态是如何转化的呢?目标金额会随着在coin中选一个硬币变小的。直到目标金额变成0了,所需要的硬币数量就是0。目标金额小于0,无解,返回-1。
    写成数学形式如下:
    d p ( n ) = { 0 , n = 0 − 1 , n < 0 min ⁡ { d p ( n − coin ⁡ ) + 1 ∣ coin ⁡ ∈ coins ⁡ } , n > 0 d p(n)=\left\{\begin{array}{l} 0, n=0 \\ -1, n<0 \\ \min \{d p(n-\operatorname{coin})+1 \mid \operatorname{coin} \in \operatorname{coins}\}, n>0 \end{array}\right. dp(n)=0,n=01,n<0min{dp(ncoin)+1coincoins},n>0

在这里插入图片描述

 def coinChange(self, coins: List[int], amount: int) -> int:
        def dp(n):
            if n == 0:
                return 0
            if n < 0:
                return -1
            # 记下最小值
            minVal = float('inf')
            # 分别算coin,构造递归树
            for coin in coins:
                subproblem = dp(n-coin)
                # 等于-1说明 n-coin 小于0了,不能兑换,就continue
                if subproblem == -1:
                    continue
                # 取硬币的最小次数
                minVal = min(minVal,subproblem+1)
            if minVal != float('inf'):
                return minVal
            else:
                return -1
        return dp(amount)

在这里插入图片描述
总结:哇哦,超出时间限制了!时间复杂度为:递归树节点个数。O(N**K)。

2.带存储列表的递归

用字典存储n时的硬币最小次数,避免重复计算。

 def coinChange(self, coins: List[int], amount: int) -> int:
        memo = dict()
        def dp(n):
            if n in memo:
                return memo[n]
            if n == 0:
                return 0
            if n < 0:
                return -1
            minVal = float('inf')
            for coin in coins:
                subproblem = dp(n-coin)
                if subproblem == -1:
                    continue
                minVal = min(minVal,subproblem+1)
            if minVal != float('inf'):
                memo[n] = minVal
            else:
                memo[n] = -1  
            return memo[n]          
        return dp(amount)

在这里插入图片描述

总结:时间复杂度:O(n)

3. 用动态转移矩阵

采用自底向上的方式

d p [ i ] = x dp[i]=x dp[i]=x表示目标金额为 i i i时,至少需要x块硬币

    def coinChange(self, coins: List[int], amount: int) -> int:
        memo = [0] + [float('inf')] * amount
        for i in range(amount+1):
            for coin in coins:
                if i >= coin:
                    memo[i] = min(memo[i],memo[i - coin]+1)
        if memo[amount] <= amount:
            return memo[amount]
        else:
            return -1

在这里插入图片描述

references

[1] Leetcode 322 零钱兑换: https://leetcode-cn.com/problems/coin-change/.
[2] labuladong的算法小抄: https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-xiang-jie-jin-jie#er-cou-ling-qian-wen-ti.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值