题解:
这是动态规划问题,动态规划问题一般用来求最值;
动态规划三要素:
状态:当前的金额数量
状态转移方程:当前金额是n,至少需要dp[n]个硬币凑出当前金额;
最优子结构:如果当前金额n=0,则返回0;如果当前金额n<0, 则返回-1;
暴力递归法:(自顶向下)
递归终止条件:
- 如果当前金额n=0,则返回0;如果当前金额n<0, 则返回-1;
详述递归过程,以coins=[1,2,5],金额11为例;
n = 11, n-1=10;
n = 10, n - 1= 9;
…
然后一直递归直到
n = 1, n - 1 = 0;
返回0后,res= 1;
然后
n = 1, n- 2= -1
------, n -5= -4
即此时 n = 1的for循环结束,返回res = 1;此时计算出了dp[1]的最优解;
返
此时n = 2, n - 1= 1时递归返回1;
此时res = 2;
然后
n = 2, n - 2 = 0;返回0,res = min(2, 1)= 1;
n = 2, n -5 = -3; 返回-1
此时循环结束,返回res=1;此时计算出了dp[2]的最优解;
返回上一层n = 3.按照此方法进行回溯。
代码如下:
class Solution:
def coinChange(self, coins, amount):
def dfs(n):
##递归终止条件
if n == 0:
return 0
if n < 0:
return -1
res = float("inf")
for i in range(len(coins)):
sub_problem = dfs(n - coins[i])
#子问题无解,跳过
if sub_problem == -1:
continue
#res记录子问题的最优解
res = min(res, 1 + sub_problem)
if res == float("inf"):
return -1
else:
return res
return dfs(amount)
但是上述方法会引入冗余计算;
如下图所示:
备忘录算法:
建立一个字典记录已经计算过的金额,避免重复计算;
class Solution:
def coinChange(self, coins, amount):
memo = dict()
#备忘录算法
def dfs(n):
if n in memo:return memo[n]
if n == 0:
return 0
if n < 0:
return -1
res = float("inf")
for coin in coins:
sub_problem = dfs(n - coin)
#子问题无解,跳过
if sub_problem == -1:
continue
#res记录子问题的最优解
res = min(res, 1 + sub_problem)
if res == float("inf"):
return -1
else:
memo[n] = res
return res
return dfs(amount)
自底向上的算法:
问题分解:
参考自leetcode的官方题解:
个人理解:
对于当前的金额i,需要遍历每一个硬币,来找i-coins的最小值;以总金额为2,coins=[1,2]为例;
F(2) = min(F(2-1),F(2-2)) + 1 = 1
代码如下,两层循环实现;第一层循环对金额进行遍历,求出每一个金额的最优硬币数量;
第二层,遍历每一个硬币,找到当前金额时需要的最小硬币数;但在进行循环之前,需要判断当前金额和硬币的大小关系,否则会出现金额小于硬币的情况,产生负数;
class Solution:
def coinChange(self, coins, amount):
dp = [amount + 1]*(amount + 1)
dp[0] = 0
for i in range(1,amount + 1):
for j in range(len(coins)):
if i >= coins[j]:
dp[i] = min(dp[i], dp[i - coins[j]] + 1)
if dp[amount] == amount + 1:
return -1
else:
return dp[amount]
这个代码和上一个代码思想一致,但是循环却是相反的;
下面这个代码是先求出在当前的一个硬币下,dp[i]的值,此时在一个硬币循环完成后,得到的dp[i]并不是最优的,而是在遍历每一个硬币的过程中,求出dp[i]的最优值;
例如金额为2,coins为[1,2]时;
硬币为1时,dp[1] = min(dp[1],dp[0] + 1) = 1, dp[2] = min(dp[2],dp[2-1] + 1) = 2;
硬币为2时,___________________________dp[2] = min(dp[2],dp[2-2] + 1) = 1
同时,下述代码在第二层循环中不用判断金额和当前硬币的大小(因为其是从当前硬币的金额大小开始进行循环的);而在第一个代码中需要判断,因为金额是从1开始循环;因此,第二个的时间复杂度是小于第一个的,效率更高。
class Solution:
def coinChange(self, coins, amount):
dp = [amount + 1]*(amount + 1)
dp[0] = 0
for i in range(len(coins)):
for j in range(coins[i], amount + 1):#避免了用if条件语句
dp[j] = min(dp[j], dp[j - coins[i]] + 1)
if dp[amount] == amount + 1:
return -1
else:
return dp[amount]