目录
322. 零钱兑换
问题描述
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11 输出:3 解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3 输出:-1
示例 3:
输入:coins = [1], amount = 0 输出:0
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 2^31 - 1
0 <= amount <= 10^4
思路与算法1:动态规划
记表示用coins表示x所需要的最少硬币数。
coins中面值比x更大的硬币肯定不能用。
- 如果coins中恰好存在等于x的硬币,则结果显然为1。
- 如果coins中没有小于等于x的硬币,则结果显然为-1。
- 如果coins中有小于等于x的硬币,假设其集合为coins_small,则可以得到状态转移方程:
为了方便以上实现,可以先对coins进行排序。前面两条构成了动态规划的基线条件。此外,如果amount等于0,则显然结果应该为0。
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
if amount==0:
return 0
coins.sort(reverse=True)
memo = dict()
def dp(x):
#print('dp({0})'.format(x))
if x in memo:
return memo[x]
ans = -1
for coin in coins:
if coin < x:
tmp = dp(x-coin)
if tmp == -1:
pass
else:
ans = tmp+1 if ans == -1 else min(ans, tmp+1)
elif coin == x:
ans = 1
break
else:
pass
memo[x] = ans
return ans
return dp(amount)
执行用时:1384 ms, 在所有 Python3 提交中击败了34.79%的用户
内存消耗:47 MB, 在所有 Python3 提交中击败了4.98%的用户
思路与算法2:广度优先搜索
本题还可以用广度优先搜索来解。
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
assert(amount >= 0)
if amount == 0:
return 0
if amount < min(coins):
return -1
q = deque([amount])
visited = set()
layer = 1
while q:
# print('layer = {0}, q.len = {1}'.format(layer,len(q)))
n = len(q)
for _ in range(n): # Traverse the current layer
a = q.pop()
if a in coins:
return layer
# Add a's neighbours into q
for c in coins:
b = a - c
if b > 0 and b not in visited:
visited.add(b)
q.appendleft(b)
layer = layer + 1
return -1
执行用时:456 ms, 在所有 Python3 提交中击败了98.80%的用户
内存消耗:15.9 MB, 在所有 Python3 提交中击败了17.40%的用户
广度优先搜索的性能要比动态规划好很多。可能是因为动态规划算法采用的递归调用式的实现方式,如果改为迭代式的实现可能会好一些吧。
518. 零钱兑换 II
问题描述
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。
例 1:
输入:amount = 5, coins = [1, 2, 5] 输出:4 解释:有四种方式可以凑成总金额: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1
示例 2:
输入:amount = 3, coins = [2] 输出:0 解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入:amount = 10, coins = [10] 输出:1
提示:
1 <= coins.length <= 300
1 <= coins[i] <= 5000
coins
中的所有值 互不相同0 <= amount <= 5000
思路与算法1:动态规划
记表示用coins表示x的所有组合数。
对于coins中所有面值小于等于x的每个coin,可以由表示
的任一组合再加上一个coin而得。由于可得状态转移方程:
基线情况:,因为不选择任何一种硬币也是一种组合
但是这样(对dp[x]进行遍历,在内层对coins进行遍历)会导致有重复计算的情况。比如说,示例1的输入[(1,2,5), 5],5==>4会得到(2,2,1)的结果,5==>2也会得到(2,2,1)的结果,同样都会得到(1,1,1,1,1)的结果。
排除组合的重复计数的关键在于遍历的顺序。如果改为外层循环对coins进行遍历,内层对dp[x]进行遍历则可以避免重复计数。
由此可以得到本题的动态规划的做法:
- 初始化
;
- 遍历
,对于其中的每个元素
,进行如下操作:
- 遍历
从
到
,将
的值加到
- 遍历
最终得到 的值即为答案。
上述做法不会重复计算不同的排列。因为外层循环是遍历数组 的值,内层循环是遍历不同的金额之和,在计算
的值时,可以确保金额之和等于
的硬币面额的顺序,由于顺序确定,因此不会重复计算不同的排列。相反,如果外层循环遍历
,内层循环
,得到的其实是考虑不同顺序的排列数。
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0]*(amount + 1)
dp[0] = 1
for i in range(len(coins)):
for j in range(coins[i], amount + 1):
dp[j] += dp[j - coins[i]]
return dp[amount]
执行用时:128 ms, 在所有 Python3 提交中击败了73.51%的用户
内存消耗:15.1 MB, 在所有 Python3 提交中击败了44.83%的用户
回到总目录:Leetcode每日一题总目录(动态更新。。。)