Intuition
Define dp[a] = the minimum number of coins that sum to amount a. To make a, the last coin used has some value c from the coin list — so dp[a] = 1 + dp[a - c] for some choice of c. We don't know which, so we try all coins and take the minimum.
Explanation
The recurrence
dp[a] = min(dp[a - c] + 1) over all coins c with c <= a. With base case dp[0] = 0 (zero coins needed for amount zero) and a sentinel INF for unreachable amounts.
Steps
- Set
INF = amount + 1(any value >amountworks as sentinel — you can never need more thanamountcoins to makeamount). - Initialise
dp = [0] + [INF] * amount. - For each amount
afrom1toamount:- For each coin
c:- If
c <= a, updatedp[a] = min(dp[a], dp[a - c] + 1).
- If
- For each coin
- Return
dp[amount]if reachable (<= amount), else-1.
Walk-through
coins = [1, 2, 5], amount = 11.
dp starts as [0, INF, INF, ..., INF].
| a | candidates dp[a-c] + 1 | dp[a] |
|---|---|---|
| 1 | dp[0]+1 = 1 | 1 |
| 2 | dp[1]+1=2, dp[0]+1=1 | 1 |
| 3 | dp[2]+1=2, dp[1]+1=2 | 2 |
| 4 | dp[3]+1=3, dp[2]+1=2 | 2 |
| 5 | dp[4]+1=3, dp[3]+1=3, dp[0]+1=1 | 1 |
| 6 | dp[5]+1=2, dp[4]+1=3, dp[1]+1=2 | 2 |
| 7 | dp[6]+1=3, dp[5]+1=2, dp[2]+1=2 | 2 |
| 8 | dp[7]+1=3, dp[6]+1=3, dp[3]+1=3 | 3 |
| 9 | dp[8]+1=4, dp[7]+1=3, dp[4]+1=3 | 3 |
| 10 | dp[9]+1=4, dp[8]+1=4, dp[5]+1=2 | 2 |
| 11 | dp[10]+1=3, dp[9]+1=4, dp[6]+1=3 | 3 |
Result: 3 (e.g., 5 + 5 + 1).
Why bottom-up vs top-down
Top-down memoised recursion solves the same recurrence but introduces function-call overhead and hits Python's recursion limit on large amounts. Bottom-up iteratively fills the table in one pass.
Why greedy doesn't work in general
"Always pick the largest coin that fits" works for [1, 5, 10, 25] but not for [1, 3, 4] with amount 6: greedy picks 4 + 1 + 1 = 3 coins, but the optimum is 3 + 3 = 2 coins. DP examines all options.
Edge cases
amount == 0:dp[0] = 0, return0.- No coin combination sums to amount:
dp[amount]stays atINF, return-1.
Complexity
- Time:
O(amount · n)wheren = len(coins). - Space:
O(amount).
Implementation
import java.util.*;
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int a = 1; a <= amount; a++) {
for (int c : coins) {
if (c <= a && dp[a - c] + 1 < dp[a]) {
dp[a] = dp[a - c] + 1;
}
}
}
return dp[amount] <= amount ? dp[amount] : -1;
}
}