Leetcode 322:零钱兑换(最详细的解法!!!)

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

示例 1:

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

示例 2:

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

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

解题思路

这实际上是一个完全背包问题,可以将amount看成是背包容量,而coins中的每个硬币对应一个商品,每种商品可以使用无数次。在这里我们先复习一下完全背包问题如何处理。我们可以定义函数 f ( x ) f(x) f(x)表示amountx的时候最少硬币个数,那么

  • f ( x ) = m i n i = 0 n f ( x − c o i n s [ i ] ) + 1 f(x)=min_{i=0}^{n}f(x-coins[i])+1 f(x)=mini=0nf(xcoins[i])+1

最后思考边界条件,因为计算最小值,所以 f ( 0... x ) = i n f f(0...x)=inf f(0...x)=inf

class Solution:
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """ 
        mem = [float('inf')]*(amount + 1)
        mem[0] = 0
        for coin in coins:
            for j in range(coin, amount + 1):
                mem[j] = min(mem[j], mem[j - coin] + 1)
        return -1 if mem[-1] > amount else mem[-1]

实际上这种问题也可以使用回溯加剪枝来处理,与之类似的问题有Leetcode 473:火柴拼正方形Leetcode 698:划分为k个相等的子集。这里我们首先将coins从大到小进行排序,这是因为我们希望硬币数量尽可能的少,那么就需要尽量将面值大的硬币加入结果中。

每次递归中判断剩余的amount%nums[index]是不是为0,如果是的话,表示当前的coins[index]可以将amount塞满,那么此时就是最优解了,记录下来;如果不是的话,那么我们只需要递归判断需要多少个coins[index]即可。如果遍历到最后一个coins[-1]且无法塞满amount的话,那么无解。

最后还有一个非常重要的剪枝,如果当前硬币coins[index](放满amount)放入后,硬币数比之前的结果都大的话,那么就不用继续判断后面的硬币了(因为硬币是排序过的)。

import math
class Solution:
    def coinChange(self, coins, amount):
        coins.sort(reverse=True)
        n, res = len(coins), amount + 1

        def dfs(index, target, cnt):
            nonlocal res
            if cnt + math.ceil(target / coins[index]) >= res:
                return 

            if target % coins[index] == 0:
                res = cnt + target // coins[index]
                return

            if index == n - 1:
                return

            for i in range(target // coins[index], -1, -1):
                dfs(index + 1, target - coins[index] * i, cnt + i)

        dfs(0, amount, 0)
        return -1 if res > amount else res

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值