#322MediumDynamic programming
Coin Change
Start dumb — and notice greedy is a trap
“Fewest coins” whispers always grab the biggest coin. It even works for
normal currency. But try coins = 1,3,4, amount = 6: greedy takes
4+1+1 — three coins — while 3+3 does it in two. Greedy commits to a
big coin without knowing what mess it leaves behind. The honest fallback
is trying every combination recursively, which explodes
exponentially.
Find the bottleneck
Watch the recursion: to price amount 11 it prices 10, 9, and 6; pricing 10 re-prices 9 and 6 again… the same sub-amounts get solved over and over. The answer for each amount never changes — recomputing it is pure waste.
The optimal shape
dp[0] = 0— paying nothing costs nothing. Everything else starts at a sentinel meaning “unreachable”.- For each amount
1..amount, try every coin that fits and keep the cheapestdp[a − coin] + 1. dp[amount]still at the sentinel means no combination works:-1.
That’s O(amount × coins) time and O(amount) space. In the player below, the array is the dp table — watch each cell settle once and never change afterward, and watch which earlier cells (highlighted) each decision reads.
Watch it think
Run the default, then the greedy trap 1,3,4 | 6 (watch cell 6 pick
3+3), an unreachable target (2 | 3), and 1 | 0 — the base case as
the entire answer.
Re-traces as you type — coin values (1–5 of them, each 1..99), a pipe, then the amount (0..24) — e.g. "1,2,5 | 11" or the greedy trap "1,3,4 | 6". Capped at 32 characters so the tiles stay readable.
Step 1 of 13: Amount 0 costs 0 coins by definition; every other cell starts at the sentinel 12 — one more than any real answer could be — so an untouched cell honestly reads "unreachable". Variables: coins = 1,2,5, amount = 11, best = —.
Data
Code
1fun coinChange(coins: IntArray, amount: Int): Int {2 val dp = IntArray(amount + 1) { amount + 1 }3 dp[0] = 04 for (a in 1..amount) {5 for (coin in coins) {6 if (coin <= a) {7 dp[a] = minOf(dp[a], dp[a - coin] + 1)8 }9 }10 }11 return if (dp[amount] > amount) -1 else dp[amount]12}
State
- coins
- 1,2,5
- amount
- 11
- best
- —
Why this step
Amount 0 costs 0 coins by definition; every other cell starts at the sentinel 12 — one more than any real answer could be — so an untouched cell honestly reads "unreachable".
1 / 13
The same pattern also solves
Recognize the window, and these fall to the same skeleton.
- Coin Change II (LC 518) ↗
Count combinations instead of minimizing — the loop order flips (coins outer) so permutations aren't double-counted.
- Perfect Squares (LC 279) ↗
The same unbounded-knapsack template with the "coins" fixed to 1, 4, 9, 16, …
- Word Break (LC 139) ↗
Same build-from-smaller-prefixes shape — dp over reachable prefixes instead of amounts.
- Climbing Stairs (LC 70) ↗
The gentlest member of the family — count ways to reach n from smaller sub-answers.