Coin Change is the classic "fewest pieces" dynamic-programming problem. It teaches a move you will reuse everywhere: build the answer for a big target out of the answers for smaller targets you have already solved.
Problem. You are given coin denominations coins and a target amount. Return the fewest number
of coins that add up to amount. You may use each coin as many times as you like. If no combination
works, return -1.
Example: coins = [1, 2, 5], amount = 6 → answer 2 (use a 5 and a 1).
The idea
Greedily grabbing the biggest coin can fail, so we solve it carefully with dynamic programming. Define
dp[a] = the fewest coins needed to make amount a. The base case is easy: dp[0] = 0, because zero coins
make the amount 0.
Now the key question for any amount a: what was the last coin I added? If the last coin was coin, then
before adding it I had already made a − coin — and the best way to do that is dp[a - coin]. So that
choice costs 1 + dp[a - coin]. We do not know which coin is last, so we try every coin and keep the
smallest:
dp[a] = min over coins of (1 + dp[a - coin])We fill dp from dp[1] up to dp[amount], left to right. Because every cell we read (dp[a - coin])
sits to the left of the cell we write (dp[a]), it is already final by the time we need it. The last
cell, dp[amount], is the answer.
Walk through it
Step through the animation. The pointer a moves right one cell at a time. At each amount we try every coin
that fits, read the cell dp[a − coin] (it lights up), add one coin, and write the smallest result
into dp[a]. The dp line underneath shows the array filling up: 0, 1, 1, 2, 2, 1, 2. The final cell
dp[6] = 2 is our answer.
Pseudocode
make an array dp of size amount + 1, filled with "infinity"
dp[0] = 0 # zero coins make amount 0
for a from 1 to amount:
for each coin in coins:
if coin <= a:
dp[a] = min(dp[a], 1 + dp[a - coin])
if dp[amount] is still "infinity": return -1
return dp[amount]We use a big sentinel value (anything > amount, since no real answer needs more than amount coins) to
mean "not reachable yet."
The Python solution
def coin_change(coins, amount):
dp = [amount + 1] * (amount + 1)
dp[0] = 0
for a in range(1, amount + 1):
for coin in coins:
if coin <= a:
dp[a] = min(dp[a], 1 + dp[a - coin])
return dp[amount] if dp[amount] <= amount else -1dphas one slot per amount, from0up toamount. We start every slot atamount + 1, a stand-in for "infinity" — no real answer can use more thanamountcoins.dp[0] = 0is the base case: it takes zero coins to make0.- The outer loop fills amounts left to right; the inner loop tries every coin.
if coin <= askips coins that are too big to fit into amounta.- Line 7 is the recurrence:
1 + dp[a - coin]means "use onecoin, plus the best way to make the rest." We keep theminacross all coins. - At the end, if
dp[amount]never improved below the sentinel, no combination works, so we return-1.
Complexity
| Case | Time | Notes |
|---|---|---|
| Try every combination (brute force) | O(coinsᵃᵐᵒᵘⁿᵗ) (moderate) | exponential — many overlapping subproblems |
| Tabulated DP (this solution) | O(amount × coins) (moderate) | each cell tried against every coin once |
O(amount) (moderate)We trade O(amount) extra space (the dp array) to collapse exponential work into a simple double loop.
Each of the amount cells does coins units of work, so the total is O(amount × coins).
When this pattern shows up
When a problem asks for the fewest / most / number of ways to reach a target, and each answer is built
from a few smaller answers, think bottom-up DP: define dp[i], find the recurrence, seed the base
case, and fill from small to large. Coin Change, Climbing Stairs, House Robber, and Unique Paths are all
the same move.
Do not be greedy. Always grabbing the biggest coin can fail — for coins = [1, 3, 4], amount = 6, greedy
gives 4 + 1 + 1 = 3 coins, but the real answer is 3 + 3 = 2. The DP tries every coin, so it never
misses the better mix.
Practice
With coins = [1, 2, 5] and dp = [0, 1, 1, 2, 2, 1] filled, what is dp[6]? Which coin gives the best result?
1. What does dp[a] mean in this solution?
2. Why is the recurrence dp[a] = min over coins of 1 + dp[a - coin]?
3. Why not just always take the biggest coin that fits (greedy)?
4. When do we return -1?