LeetCode 322 零钱兑换
f810183385414c1a91cd70de301e20c5
题目:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
**输入:**coins = [1, 2, 5], amount = 11
**输出:**3
**解释:** 11 = 5 + 5 + 1
题意解析: 有一堆不同面额的硬币,问最少取多少枚硬币,可以凑够想要的面值。
DFS解析:
- 为了最终硬币数量最少,首先对硬币面值从大到小排序
- 因为面值大的越靠前,最后所需额度也会相应减小,硬币数量也会减少
- 构建DFS函数
- 确定边界,若不存在目标金额,则返回当前最小值
- 遍历硬币面值: 若存在当前硬币面值小于等于目标金额且目标金额小于边界条件(防止溢出),则drill down,进入下一层循环寻找剩余金额所需硬币
- 设置目标金额,遍历硬币面值,求解
def coinChange(self, coins, amount):
coins.sort(reverse = True)
coins_len, self.result = len(coins), float(“inf”)
def dfs(index, target, count):
if not target:
self.res = min(self.res, count)
for i in range(index, coins_len):
if coins[i] <= target < coins[i] * (self.result-count): # if hope still exists
dfs(i, target-coins[i], count+1)
for i in range(coins_len):
dfs(i, amount, 0)
return self.result if self.result < float(“inf”) else -1
剪枝
上面方式遍历过程中并不需要全部遍历全部结果,可以通过剪枝,去除多余的选项:
- 同上对硬币面值排序(从大到小)
- 构建countCoins函数:
- 设置金额上限及硬币上限,若当前金额大于目标金额则返回
- 求模,针对当前硬币和目标金额取模,若满足条件即表示当前的coin可以将目标金额填满,只需要计算coins即可
- 遍历目标金额,从大至小,依次递减1: 若存在目标金额与对应面值的硬币匹配的情况(求模),则将硬币记录下来
- 针对目标金额遍历求值即可
import math
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
coins.sort(reverse = True)
coins_len, result = len(coins), amount + 1
def countCoins(index, target, count):
if count + math.ceil(target/coins[index]) >= result:
return
if target % coins[index] == 0:
result = count + target//coins[index]
return
if index == coins_len - 1:
return
for i in range(target//coins[index], -1, -1):
countCoins(index+1, target - coins[index]*i, count+1)
countCoins(0, amount, 0)
return -1 if result > amount else result
BFS 解析
- 将问题转化为求X轴0点到坐标点amount的最短距离 (每次向前行进的合法距离为coin的面值)
- visited = [False]*(amount+1) 构建数组,存放后续遍历的节点
- 和DFS类似,先遍历对应硬币的面值 在这个过程中,计算当前硬币面值与已有硬币的和集,比较当前金额和目标金额的差异(相等则返回并记录硬币,超过目标金额则放弃,不处理) 若 当前金额不在已访问过的数组中,则将当前金额添加至已访问过的数组中
- 返回遍历后的结果
class Solution(object):
def coinChange(self, coins, amount):
if amount == 0:
return 0
value1 = [0]
value2 = []
nc = 0
visited = [False]*(amount+1)
visited[0] = True
while value1:
nc += 1
for v in value1:
for coin in coins:
newval = v + coin
if newval == amount:
return nc
elif newval > amount:
continue
elif not visited[newval]:
visited[newval] = True
value2.append(newval)
value1, value2 = value2, []
return -1
动态规划解析:
- 首先声明一个大小为amount+1的数组dp,用dp[i]存储”对于金额amount最少用到的硬币数coins”
- 为什么大小是amount+1?比如amount是11块,dp要从0元开始存储到11块,所以数组的大小要amount+1
- 对于初始化数组dp的索引i=0的元素值为0,是因为0块要的硬币数为0,所以初始化为0
- 根据数组dp的定义,得到方程: dp[i] = min(dp[i-coin]+1) 注意:dp[i-coin]的coin是针对每一种硬币
- 返回值
- dp[amount]返回dp数组的最后一个元素,dp[amount] == float(“inf”)返回dp数组最后的元素是否为inf(无穷大)
- 这里用一个例子解释[3, -1][True]返回的是索引为1的元素,[3, -1][False]返回索引为0的元素
- 所以dp[amount]不等于inf则返回dp[amount],等于inf则返回-1,意味着任何一种硬币组合能组成总金额
time: O(amount*len(coins) 第一层循环遍历了amount次,第二层循环遍历了数组coins的每个元素 space: O(amount) 算法用一个大小为amount+1的数组来存储值
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [0] + [float(“inf”)] * amount
for i in range(1, amount + 1):
dp[i] = min([dp[i - c] if i - c >= 0 else float(“inf”) for c in coins]) + 1
return [dp[amount], -1][dp[amount] == float(‘inf’)]
如上方式可以转换如下通俗的方式:
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
n = len(coins)
# dp[i]表示金额amount需要的最少coins数
dp = [float("inf")] * (amount+1)
dp[0] = 0
for i in range(amount+1):
for j in range(n):
if coins[j] <= i:
dp[i] = min(dp[i], dp[i-coins[j]]+1)
return dp[amount] if dp[amount] <= amount else -1
- 题目:
- DFS解析:
- 剪枝
- BFS 解析
- 动态规划解析: