给定不同面额的硬币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)表示amount
为x
的时候最少硬币个数,那么
- 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(x−coins[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
如有问题,希望大家指出!!!