1,递归+记忆化搜索。
从amount开始向下查找,找到0以后回来更新步数。
我的第一个写法,错误的
java
public int f(int[] coins, int amount, int step){
if(amount == 0) return step;
int temp_step = Integer.MAX_VALUE;
for(int i = 0; i < coins.length; i++){
int last = amount - coins[i];
if(last < 0)continue;
temp_step = Math.min(temp_step, f(coins, last, step + 1));
}
return temp_step ;
}
public int coinChange(int[] coins, int amount) {
int res = f(coins, amount, 0);
return res == Integer.MAX_VALUE? -1 : res;
}
错误原因
step 这个参数是"外部状态",它让子问题无法复用(也是你之前版本缓存语义错的根源)
你现在虽然没缓存,但 step 的存在本质上说明:
-
你在求的是"从初始调用累计到现在的总步数"
-
而经典 coinChange 子问题应该求的是:"凑出 amount 的最少硬币数"(与从哪一层进来无关)
所以只要你想优化(加 memo),就会立刻遇到语义问题:f(amount, step) 不能只用 amount 做 key(因为 step 变了返回值就变了),导致 memo 很难做、或者做了也错。
✅ 正确做法:让递归函数只依赖 amount,返回"从 amount 到 0 的最少硬币数",不要带 step。
用我们能听得懂的话来说,这个step的含义是从初始调用累计到现在的总步数,并不会被路径上的节点更新
正确解法
java
static final int N = (int) (1e4 + 10);
int[] has_use = new int[N];
public int coinChange(int[] coins, int amount){
if(amount == 0) return 0;
if(has_use[amount] != 0)return has_use[amount];
int temp_step = Integer.MAX_VALUE;
for(int i = 0; i < coins.length; i++){
int last = amount - coins[i];
if(last < 0)continue;
int r = coinChange(coins, last);
if(r == -1)continue;
temp_step = Math.min(temp_step, r + 1);
}
temp_step = (temp_step == Integer.MAX_VALUE) ? -1 : temp_step;
has_use[amount] = temp_step;
return temp_step ;
}
换成自底向上的解法,dp
java
public int coinChange2dp(int[] coins, int amount){
int[] dp = new int[amount+1];
for(int i = 1; i <= amount; i++){
dp[i] = Integer.MAX_VALUE;
for(int j = 0; j < coins.length; j++){
int last = i - coins[j];
if(last < 0)continue;
if(dp[last] == -1)continue;
dp[i] = Math.min(dp[i], dp[last] + 1);
}
dp[i] = (dp[i] == Integer.MAX_VALUE) ? -1 : dp[i];
}
return dp[amount];
}