LeetCode:322. 零钱兑换(python)

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

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3
输出: -1

说明:你可以认为每种硬币的数量是无限的。

LeetCode 链接


思路:
  1. 递归(超时),递归遍历添加所有硬币的情况,每次添加某硬币,则递归遍历总金额-某硬币 的子问题。
  • 当前总金额'0 则表明当前硬币情况有解,返回 0
  • 当前总金额' 不为 0,则继续添加硬币
  • 当前总金额'-硬币<0 表明不能添加该硬币(无解),尝试添加其他硬币。
  • 当前总金额'-硬币>0递归遍历 当前总金额-硬币 的子问题,若子问题无解,尝试添加其他硬币;若子问题有解,则每次添加硬币时记录,硬币数 +1,最终比较取最小值。
  • 返回情况,若子问题有解,则返回子问题的 最少硬币数,否则返回 -1
  1. 记忆递归,以上递归所有硬币情况存在大量重复,添加记忆字典 {当前总金额:值}进行剪枝,值为 -1 表明,当前总金额的情况无解,否则值为当前总金额需要的最少硬币数。
  2. 动态规划
  • 状态:金额 0 至金额 amountdp 数组记录当前金额的最少硬币数。
  • 选择当前金额 下是否添加 该硬币(遍历所有种类硬币情况),取决于所需硬币数的多少,若不添加则维持原硬币数;若添加则更新为 dp[当前金额-该硬币]+1
  • 初始值
  • dp 数组维度为 1×(amount+1),代表金额状态 0~amount;值为无穷大,方便比较最小值(所需硬币数最少)
  • dp[0]=0,表示金额为 0 时,所需硬币数为 0
  • 返回值:判断 dp[amout]
  • 若为无穷大,则表明该金额无法用所给的硬币种类求解;
  • 否则返回 amount 金额下所需的最少硬币数 dp[amount]
附代码1(Python3):递归(超时)
class Solution(object):
    def coinChange(self, coins, amount):
        if amount == 0:                                     # amount 为 0 表示当前硬币情况有解,则返回 0
            return 0
        res = float('inf')
        for coin in coins:
            if amount-coin < 0:                             # 若添加该硬币无解则尝试其他硬币
                continue
            sub_count = self.coinChange(coins, amount-coin) # 递归遍历子问题
            if sub_count == -1:                             # 若子问题无解则尝试其他硬币
                continue
            res = min(res, sub_count+1)                     # 子问题有解,硬币数加 1,取最小值
        return res if res!=float('inf') else -1             # 子问题有解则返回 res 值,无解则返回 -1
test = Solution()
coins_li = [[1,2,5],
            [2]
           ]
amount_li = [11, 3]
for coins, amount in zip(coins_li, amount_li):
    print(test.coinChange(coins, amount))
3
-1
附代码2(Python3):记忆递归
class Solution(object):
    def coinChange(self, coins, amount):
        memo = {}              # 记忆
        return self.recurve(coins, amount, memo)
    
    def recurve(self, coins, amount, memo):
        if amount == 0:
            return 0
        if amount in memo:
            return memo[amount]        

        res = float('inf')
        for coin in coins:
            if amount-coin < 0:
                continue
            sub_count = self.recurve(coins, amount-coin, memo)
            if sub_count == -1:
                continue
            res = min(res, sub_count+1)
        if res != float('inf'):
            memo[amount] = res
            return memo[amount]
        else:
            memo[amount] = -1
            return memo[amount]
test = Solution()
coins_li = [[1,2,5],
            [2]
           ]
amount_li = [11, 3]
for coins, amount in zip(coins_li, amount_li):
    print(test.coinChange(coins, amount))
3
-1
附代码3(Python3):动态规划
class Solution(object):
    def coinChange(self, coins, amount):
        dp = [float('inf')]*(amount+1)
        dp[0] = 0
        for i in range(1, amount+1):
            for coin in coins:
                if coin <= i:
                    dp[i] = min(dp[i], dp[i-coin]+1)
        if dp[amount] == float('inf'):               # 该金额无解,则返回 -1
            return -1
        else:
            return dp[amount]                       # 该金额有解,则返回最少硬币数
test = Solution()
coins_li = [[1,2,5],
            [2]
           ]
amount_li = [11, 3]
for coins, amount in zip(coins_li, amount_li):
    print(test.coinChange(coins, amount))
3
-1