leetcode 322 零钱兑换
[toc]
1. 题目
- 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
2. 解法
2.1. 暴力穷举
暴力穷举实质就是一个个方案试,如果满足条件则记录其使用的硬币数量,再从中取最小值。
2.1.1. 暴力穷举1
注意:并不需要穷尽所有组合,从大数开始组合,求出一个有效组合的硬币数作为最小值过滤器,可以减少无效递归次数;
def coinChange_1(self, coins, amount):
"""
暴力穷举
递归
def _helper(coins, num, leftamount, cur_coins_list):
递归函数,coins:硬币池; num:下标; leftamount:剩余钱数; cur_coins_list:当前硬币序列
时间复杂度:O(S^N);因为最坏情况下每种硬币最多可能有S/Ci个;
所以可能的组合数为S/C1 * S/C2 * S/C3 ......S/Cn=S^N/C1*C2*C3......Cn
简化一下就是S^N
S为金额,N为硬币种数。
空间复杂度:O(N)最坏情况下,递归的最大深度为N。
:param coins:
:param amount:
:return:
"""
assert coins is not None and amount > 0, 'value error.'
mincount = amount + 1
# 反转coins排序方式为大至小
coins = list(reversed(coins))
# 递归函数
def _helper(coins, num, leftamount, cur_coins_list):
nonlocal mincount
cur_coins_list.append(coins[num])
length = len(cur_coins_list)
if leftamount == 0:
if length < mincount:
mincount = length
elif leftamount > 0:
if length >= mincount:
return
for i in range(num, len(coins)):
_helper(coins, i, leftamount-coins[i], cur_coins_list[:])
for x in range(len(coins)):
_helper(coins, x, amount - coins[x], [])
return -1 if mincount > amount else mincount
2.1.2. 暴力穷举2
同样的逻辑,换个方式。
def coinChange_1_1(self, coins, amount):
"""
暴力穷举
:param coins:
:param amount:
:return:
"""
coins = list(reversed(coins))
def _helper(index, coins, amount):
if amount == 0: return 0
if index < len(coins) and amount > 0:
mincost = float('inf')
for i in range(0, amount//coins[index]+1):
if amount >= i * coins[index]:
res = _helper(index+1, coins, amount - i * coins[index])
if res != -1:
mincost = min(mincost, res+i)
return -1 if mincost == float('inf') else mincost
return -1
return _helper(0, coins, amount)
2.2. 动态规划-1 自下而上
定义一维数组dp
dp[i]的值为组合成i时需要的最少硬币数,那么继续向前推就是dp[i]=dp[i-coin[j]] 需要的最少硬币数 +1,+1代表使用coin[j]一次。
原理:
定义数组dp[i][j],i为硬币数量,j为总金额,该处元素定义为如果j可以由coins[0-i]中的硬币组成,则值为组成方案所需硬币数量的最小值。 设dp[x][y]存在解,那么dp[x][y-coin[x]]一定有解(除非y小于x), 而且dp[x][y]的可能值为dp[x][y-coin[x]] + 1 (在前者的基础上多加了一个coin[x]) 但是,注意列j代表此金额下的所有解,所以如果dp[x-1][y]存在解,意味着该解对dp[x][y]也是有效的。 题目要求的是解的最小值硬币数,所以二者中取最小值: dp[x][y] = min(dp[x][y-coin[x]], dp[x-1][y]) 当然,这是一个二维数组,但实际中并不关心[x-2]及以前行的数据,而且是逐一向后迭代的,所以可以复用简化为一维数组。
代码:
def coinChange_2(self, coins, amount):
"""
动态规划
从下至上
:param coins:
:param amount:
:return:
"""
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for x in range(coin, amount + 1):
dp[x] = min(dp[x], dp[x-coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
2.3. 动态规划-2 自上而下
递归法的变种,使用数组记录已求过的解。
def coinChange_2_2(self, coins, amount):
"""
动态规划/递归穷举/回溯法
自上而下
:param coins:
:param amount:
:return:
"""
def _helper(coins, leftamount, count_list):
if leftamount < 0: return -1
if leftamount == 0: return 0
if count_list[leftamount-1] != 0: return count_list[leftamount - 1]
mincount = float('inf')
for x in range(len(coins)):
res = _helper(coins, leftamount - coins[x], count_list)
if res >= 0 and res < mincount:
mincount = res + 1
count_list[leftamount - 1] = mincount if mincount != float('inf') else -1
return count_list[leftamount - 1]
return _helper(coins, amount, [0]*amount)
3. 代码及测试结果
3.1. 测试代码:
if __name__ == "__main__":
# 实例化解决方案类
so = Solution()
# 参数设定
li = [1,2,5,10,20]
para = (li, 118)
test_func(so, para)
pass
3.2. 结果:
共计有<4>个方法: ['coinChange_1_1', 'coinChange_1_2', 'coinChange_2_1', 'coinChange_2_2']
****************************************
方法[1]:coinChange_1_1
说明:暴力穷举
递归
def _helper(coins, num, leftamount, cur_coins_list):
递归函数,coins:硬币池; num:下标; leftamount:剩余钱数; cur_coins_list:当前硬币序列
时间复杂度:O(S^N);因为最坏情况下每种硬币最多可能有S/Ci个;
所以可能的组合数为S/C1 * S/C2 * S/C3 ......S/Cn=S^N/C1*C2*C3......Cn
简化一下就是S^N
S为金额,N为硬币种数。
空间复杂度:O(N)最坏情况下,递归的最大深度为N。
:param coins:
:param amount:
:return:
执行结果: 9
****************************************
方法[2]:coinChange_1_2
说明:暴力穷举
:param coins:
:param amount:
:return:
执行结果: 9
****************************************
方法[3]:coinChange_2_1
说明:动态规划
从下至上
:param coins:
:param amount:
:return:
执行结果: 9
****************************************
方法[4]:coinChange_2_2
说明:动态规划/递归穷举/回溯法
自上而下
:param coins:
:param amount:
:return:
执行结果: 9
执行时长:
('coinChange_1_1', 0.0018791932531923659)
('coinChange_1_2', 0.10514478138982856)
('coinChange_2_1', 0.00021833724987438408)
('coinChange_2_2', 0.0006242205989998584)
Process finished with exit code 0
日拱一卒无有尽,功不唐捐终入海

浙公网安备 33010602011771号