leetcode 322 零钱兑换
Posted 木林森__
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode 322 零钱兑换相关的知识,希望对你有一定的参考价值。
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 04>>>>>
以上是关于leetcode 322 零钱兑换的主要内容,如果未能解决你的问题,请参考以下文章