给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
思路1:递归
类似问题: 39、组合总和【中等】
不同之处:超时了
def change( amount, coins) :
def dfs(begin,target):
if target<0:
return
if target==0:
res[0]+=1
return
for i in range(begin,len(coins)):
resuid=target-coins[i]
dfs(i,resuid)
dfs(0,amount)
return res[0]
思路2:动态规划 / 背包问题
类似问题:70、爬楼梯【简单】
- 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
不同之处:爬楼梯是【排列】问题,零钱兑换是【组合】问题
爬楼梯:DP[i] = DP[i-1] = DP[i-2]
背包问题:
- 填满容量为
j
的背包,有dp[j]
种方法 - 填满容量为
j - nums[i]
的背包,有dp[j - nums[i]]
种方法。 - 已经有了
nums[i] = 2
,那么填满一个容量为5的背包,有dp[3]
种方法
递推公式:
遍历nums[i]
,把所有的dp[j - nums[i]]
累加起来:
dp[j] =dp[j]+ dp[j - nums[i]]
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp=[0 for _ in range(amount+1)]#多一位
dp[0]=1#没有硬币
for coin in coins:
#coin进来以后,比coin小的数的组合肯定不会变啦
for i in range(coin,amount+1):
dp[i]+=dp[i-coin]
return dp[amount]#dp里的第六个数即dp[5]
注意!不能写成:
for i in range(1,amount+1):
for coin in coins:
dp[i]+=dp[i-coin]
- 外层 coin 循环实际上是表示每次有新的 coin 参与进来,dp[x] 的值就会更新,最后直到所有的 硬币都参与选择的时候 dp[x]才有定值 ;
- 内层循环是总金额 dp[x]在内层循环里面不停的在更新。主要是求组合数,coin 必须写在外面去重,不然写在内层循环的话是求排列数
- 所以要全部使用完一种硬币,保证后面不会再用到