思路
回溯 超时
做这种题,一般先画出递归树,辅助我们理解过程,可以看到分支中存在很多重复计算,为我们优化提供了方向;
void dfs(vector<int>& coins, int amount,int& res,int ans){
if(amount == 0){
res = min(res,ans);
return ;
}
for(int i=0;i<coins.size();i++){
if(coins[i] <= amount){
dfs(coins,amount-coins[i],res,ans+1);
}
}
}
int coinChange(vector<int>& coins, int amount) {
if(amount==0) return 0;
int res =INT_MAX;
dfs(coins,amount,res,0);
return res;
}
回溯+备忘录 自顶向下
可以使用dp[]
记录每一步计算过程,这样就不会计算两次重复子问题,比如
f(11) -2-> f(9)
通过2
走到f(9)
,和通过f(11) -1-> f(10) -1-> f(9)
,都可以走到f(9)
,但是上面的方面会计算两次f(9)
,造成很多重复运算
int dfs(vector<int>& coins, int amount,vector<int>& dp){
if(amount < 0) return -1;
if(amount == 0) return 0;
if(dp[amount] != 0) return dp[amount];
int a = INT_MAX;
for(int i=0;i<coins.size();i++){
//计算 f(amount-i) 可能值的最小个数
// f(3) = min{f(2),f(1),f(-2)}+1;
int tmp = dfs(coins,amount-coins[i],dp);
if(tmp >= 0 && tmp<a){
a = tmp+1;
}
}
dp[amount]= a==INT_MAX?-1:a;
return dp[amount];
}
int coinChange(vector<int>& coins, int amount) {
if(amount==0) return 0;
vector<int> dp(amount+1,0);
dfs(coins,amount,dp);
return dp[amount];
}
DP 自底向上
其实就是记忆化搜索的反过程,从0开始向上递推,
dp[i] = min(dp[i],dp[ i- coins[j] ]+1) 0<j<i
求dp[i]
时需要从之前存在的状态中逐步推出现有状态
int coinChange(vector<int>& coins, int amount) {
if(amount==0) return 0;
vector<int> dp(amount+1,amount+1);
dp[0] = 0;
for(int i=1;i<=amount;i++){
for(int coin:coins){
if(i-coin >= 0){
dp[i] = min(dp[i],dp[i-coin]+1);
}
}
}
return dp[amount]>amount?-1:dp[amount];
}