Coin Change (LeetCode 322)
You're given an array coins of distinct coin denominations and a target amount, with an unlimited supply of every denomination.
Find the smallest number of coins whose values add up to exactly amount. If no combination of coins reaches that amount exactly, return -1.
Worked Examples
coins = [1,2,5], amount = 113coins = [2], amount = 3-12 can only make even amounts, never 3.coins = [1], amount = 000 needs no coins.⚖️Formal Constraints & Bounds
1 <= coins.length <= 121 <= coins[i] <= 231 - 10 <= amount <= 104
Core Insight
Choice Continuity allows the full coin denomination pool to be looped at every recursion frame. Because any remaining amount 'rem' is path-independent, caching memo[rem] collapses exponential O(C^A) search into linear O(C * A) subproblems (where C = len(coins), A = amount).
Real-World Scenario & Production Applications
Vending machines, currency dispensing, and resource optimization where identical denominations can be reused continuously.
Subproblems & Recurrence Decomposition3 Phases
Zero coins are needed to make remaining amount 0 (return 0). An overshoot (rem < 0) is an invalid choice, represented as float('inf') so it never wins a min() comparison.
if rem == 0:
return 0
if rem < 0:
return float('inf')Step-by-Step Execution Trace Table
rem=3 (Root, d=0) / | \ choose 1 / choose 2 | choose 5 \ (rem - 5 < 0) / | \ rem=2 (d=1) rem=1 (d=1) ✂️ PRUNED (rem < 0 -> inf) / \ | choose 1 / \ choose 2 | choose 1 / \ | rem=1 (d=2) rem=0 ✅ rem=0 ✅ (1 coin + 1) | (1 coin + 1) (memo[1] = 1) choose 1| (d=3) | rem=0 ✅ (3 coins) Step-by-Step Decision Trace
- Step 1 (Root, rem=3, d=0): Allocate
memo = [-1] * 4. Calldfs(3). Loop coins[1, 2, 5]. - Step 2 (Choose 1, rem=2, d=1): Call
dfs(2). Loop coins[1, 2, 5]. - Step 3 (Choose 1, rem=1, d=2): Call
dfs(1). Loop coins[1, 2, 5]. - Step 4 (Choose 1, rem=0, d=3): Base Case reached (
rem == 0): return0coins needed. - Step 5 (Compute rem=1, d=2): Coin
1returns0ans = min(inf, 0 + 1) = 1. Coins2, 5yieldrem < 0(✂️ PRUNED). Cachememo[1] = 1, return1. - Step 6 (Backtrack to d=1, Choose 2, rem=0, d=2): Call
dfs(2 - 2 = 0)returns0.ans = min(2, 0 + 1) = 1. Cachememo[2] = 1, return1. - Step 7 (Backtrack to d=0, Choose 2, rem=1, d=1): Call
dfs(3 - 2 = 1). Cache hit (memo[1] == 1): returns1in !ans = min(2, 1 + 1) = 2. - Step 8 (Choose 5, rem=-2, d=1):
3 - 5 = -2 < 0✂️ PRUNED (rem < 0returnsinf). - Step 9 (Record Optimal Solution, d=0): Cache
memo[3] = 2. Record Minimum Coins =2✅. Search Complete: Minimum coins needed =2.
coins = [1,2,5], amount = 11Expected:3| 1 | memo = [-1] * (amount + 1) |
| 2 | def dfs(rem): |
| 3 | if rem == 0: |
| 4 | return 0 |
| 5 | if rem < 0: |
| 6 | return float('inf') |
| 7 | if memo[rem] != -1: |
| 8 | return memo[rem] |
| 9 | ans = float('inf') |
| 10 | for coin in coins: |
| 11 | res = dfs(rem - coin) |
| 12 | if res != float('inf'): |
| 13 | ans = min(ans, res + 1) |
| 14 | memo[rem] = ans |
| 15 | return ans |
| 16 | best = dfs(amount) |
| 17 | return best if best != float('inf') else -1 |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Coin Change asks for the minimum number of coins needed to make up a given amount. Expressed as a decision tree, at any remaining amount , you can branch into choosing any coin , transitioning to subproblem . However, because coin denominations can be reused in any order (e.g. paying 1 then 2 vs 2 then 1), pure backtracking explores the same remaining amounts repeatedly, triggering an astronomical exponential blowup. By attaching a memoization cache memo[remain], identical subproblems collapse into a single calculation.
🏟️ The Analogy: A Subway Fare Machine with Memory
Imagine an automated transit machine calculating the fewest coins to refund 87 cents. If the machine dispenses a quarter (leaving 62 cents), it starts figuring out the optimal way to make 62 cents. If along a completely different calculation path it dispenses two dimes and a nickel (also leaving 62 cents), a naive machine would re-calculate the optimal 62-cent payout from scratch. A machine with memory writes on a digital notepad: "Optimal coins for 62 cents = 5 coins." The next time any coin branch leaves 62 cents, it reads the stored answer instead of recalculating it.
🔑 Why It Works
The decision tree's overlapping subproblems are parameterized entirely by the scalar remain. Memoizing the optimal count for each remaining value converts an exponential recursive tree into a DAG(Directed Acyclic Graph), guaranteeing runtime.
Omission of remainder memoization: Recursively branching without caching
memo[rem]results in exponential explosion. Caching the minimum coins for each remainder reduces runtime to .Base case return offset: Returning
1instead of0whenrem == 0. Zero remaining value requires zero additional coins. Returning 1 shifts the answer by +1.Unreachable amount sentinel bug: Returning
float('inf')directly to the caller instead of converting to-1when no combination can reach the target amount.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Find minimum coins/elements to make a target sum + unlimited coin reuse allowed + choice continuity.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: remaining tracks remaining amount. Optimal subproblem depends strictly on remaining, making memoization effective.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Missing memoization: exploring all C^A paths without caching causes severe time-limit-exceeded (TLE).
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I use memoized backtracking over remaining amount. For each coin, I recursively compute the minimum coins needed for
remaining - coin. I cache results inmemoto avoid re-evaluating identical subproblems. Time complexity is O(amount * len(coins)) with O(amount) recursion and memo space."
Complexity & Mathematical Proof
O(Amount · N)
There are Amount + 1 subproblems (0 to Amount). Each subproblem iterates through N coin denominations once. Total runtime is O(Amount · N).
O(Amount) Auxiliary
Memoization dictionary/table of size Amount + 1 and recursion call stack of max depth Amount.
T(A, N) = (A + 1) · N = O(A · N)
Derivation Progression
A + 1 states for amounts 0 dots A
Memoization ensures each remainder amount is computed at most once.
N coin branches per state
Evaluates 1 + minCoins(rem - c) across all N coins.
O(A · N)
Eliminates exponential branching completely.
Variable Definitions
Target amount value (0 to Amount subproblems)
Number of coin denominations available
Memory Architecture & Bounds
O(A) Recursion stack depth (worst case coin 1)
O(A) Memoization cache table
O(1) Min coins integer
Boundary Best / Worst Cases
if amount == 0.
.
with coin 1 available.
Decision & State-Space Search Tree
Senior SWE Deconstruction & Hardware Caveats
Triggers: "fewest number of coins", "unlimited supply". Top-down memoized backtracking: state depends solely on remaining amount rem.
. Memoization reduces exponential tree to .
Unmemoized exponential explosion. Return if amount cannot be formed.
Coin Change (LeetCode 322)
You're given an array coins of distinct coin denominations and a target amount, with an unlimited supply of every denomination.
Find the smallest number of coins whose values add up to exactly amount. If no combination of coins reaches that amount exactly, return -1.
Worked Examples
coins = [1,2,5], amount = 113coins = [2], amount = 3-12 can only make even amounts, never 3.coins = [1], amount = 000 needs no coins.⚖️Formal Constraints & Bounds
1 <= coins.length <= 121 <= coins[i] <= 231 - 10 <= amount <= 104
Core Insight
Choice Continuity allows the full coin denomination pool to be looped at every recursion frame. Because any remaining amount 'rem' is path-independent, caching memo[rem] collapses exponential O(C^A) search into linear O(C * A) subproblems (where C = len(coins), A = amount).
Real-World Scenario & Production Applications
Vending machines, currency dispensing, and resource optimization where identical denominations can be reused continuously.
Subproblems & Recurrence Decomposition3 Phases
Zero coins are needed to make remaining amount 0 (return 0). An overshoot (rem < 0) is an invalid choice, represented as float('inf') so it never wins a min() comparison.
if rem == 0:
return 0
if rem < 0:
return float('inf')Step-by-Step Execution Trace Table
rem=3 (Root, d=0) / | \ choose 1 / choose 2 | choose 5 \ (rem - 5 < 0) / | \ rem=2 (d=1) rem=1 (d=1) ✂️ PRUNED (rem < 0 -> inf) / \ | choose 1 / \ choose 2 | choose 1 / \ | rem=1 (d=2) rem=0 ✅ rem=0 ✅ (1 coin + 1) | (1 coin + 1) (memo[1] = 1) choose 1| (d=3) | rem=0 ✅ (3 coins) Step-by-Step Decision Trace
- Step 1 (Root, rem=3, d=0): Allocate
memo = [-1] * 4. Calldfs(3). Loop coins[1, 2, 5]. - Step 2 (Choose 1, rem=2, d=1): Call
dfs(2). Loop coins[1, 2, 5]. - Step 3 (Choose 1, rem=1, d=2): Call
dfs(1). Loop coins[1, 2, 5]. - Step 4 (Choose 1, rem=0, d=3): Base Case reached (
rem == 0): return0coins needed. - Step 5 (Compute rem=1, d=2): Coin
1returns0ans = min(inf, 0 + 1) = 1. Coins2, 5yieldrem < 0(✂️ PRUNED). Cachememo[1] = 1, return1. - Step 6 (Backtrack to d=1, Choose 2, rem=0, d=2): Call
dfs(2 - 2 = 0)returns0.ans = min(2, 0 + 1) = 1. Cachememo[2] = 1, return1. - Step 7 (Backtrack to d=0, Choose 2, rem=1, d=1): Call
dfs(3 - 2 = 1). Cache hit (memo[1] == 1): returns1in !ans = min(2, 1 + 1) = 2. - Step 8 (Choose 5, rem=-2, d=1):
3 - 5 = -2 < 0✂️ PRUNED (rem < 0returnsinf). - Step 9 (Record Optimal Solution, d=0): Cache
memo[3] = 2. Record Minimum Coins =2✅. Search Complete: Minimum coins needed =2.
coins = [1,2,5], amount = 11Expected:3| 1 | memo = [-1] * (amount + 1) |
| 2 | def dfs(rem): |
| 3 | if rem == 0: |
| 4 | return 0 |
| 5 | if rem < 0: |
| 6 | return float('inf') |
| 7 | if memo[rem] != -1: |
| 8 | return memo[rem] |
| 9 | ans = float('inf') |
| 10 | for coin in coins: |
| 11 | res = dfs(rem - coin) |
| 12 | if res != float('inf'): |
| 13 | ans = min(ans, res + 1) |
| 14 | memo[rem] = ans |
| 15 | return ans |
| 16 | best = dfs(amount) |
| 17 | return best if best != float('inf') else -1 |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Coin Change asks for the minimum number of coins needed to make up a given amount. Expressed as a decision tree, at any remaining amount , you can branch into choosing any coin , transitioning to subproblem . However, because coin denominations can be reused in any order (e.g. paying 1 then 2 vs 2 then 1), pure backtracking explores the same remaining amounts repeatedly, triggering an astronomical exponential blowup. By attaching a memoization cache memo[remain], identical subproblems collapse into a single calculation.
🏟️ The Analogy: A Subway Fare Machine with Memory
Imagine an automated transit machine calculating the fewest coins to refund 87 cents. If the machine dispenses a quarter (leaving 62 cents), it starts figuring out the optimal way to make 62 cents. If along a completely different calculation path it dispenses two dimes and a nickel (also leaving 62 cents), a naive machine would re-calculate the optimal 62-cent payout from scratch. A machine with memory writes on a digital notepad: "Optimal coins for 62 cents = 5 coins." The next time any coin branch leaves 62 cents, it reads the stored answer instead of recalculating it.
🔑 Why It Works
The decision tree's overlapping subproblems are parameterized entirely by the scalar remain. Memoizing the optimal count for each remaining value converts an exponential recursive tree into a DAG(Directed Acyclic Graph), guaranteeing runtime.
Omission of remainder memoization: Recursively branching without caching
memo[rem]results in exponential explosion. Caching the minimum coins for each remainder reduces runtime to .Base case return offset: Returning
1instead of0whenrem == 0. Zero remaining value requires zero additional coins. Returning 1 shifts the answer by +1.Unreachable amount sentinel bug: Returning
float('inf')directly to the caller instead of converting to-1when no combination can reach the target amount.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Find minimum coins/elements to make a target sum + unlimited coin reuse allowed + choice continuity.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: remaining tracks remaining amount. Optimal subproblem depends strictly on remaining, making memoization effective.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Missing memoization: exploring all C^A paths without caching causes severe time-limit-exceeded (TLE).
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I use memoized backtracking over remaining amount. For each coin, I recursively compute the minimum coins needed for
remaining - coin. I cache results inmemoto avoid re-evaluating identical subproblems. Time complexity is O(amount * len(coins)) with O(amount) recursion and memo space."
Complexity & Mathematical Proof
O(Amount · N)
There are Amount + 1 subproblems (0 to Amount). Each subproblem iterates through N coin denominations once. Total runtime is O(Amount · N).
O(Amount) Auxiliary
Memoization dictionary/table of size Amount + 1 and recursion call stack of max depth Amount.
T(A, N) = (A + 1) · N = O(A · N)
Derivation Progression
A + 1 states for amounts 0 dots A
Memoization ensures each remainder amount is computed at most once.
N coin branches per state
Evaluates 1 + minCoins(rem - c) across all N coins.
O(A · N)
Eliminates exponential branching completely.
Variable Definitions
Target amount value (0 to Amount subproblems)
Number of coin denominations available
Memory Architecture & Bounds
O(A) Recursion stack depth (worst case coin 1)
O(A) Memoization cache table
O(1) Min coins integer
Boundary Best / Worst Cases
if amount == 0.
.
with coin 1 available.
Decision & State-Space Search Tree
Senior SWE Deconstruction & Hardware Caveats
Triggers: "fewest number of coins", "unlimited supply". Top-down memoized backtracking: state depends solely on remaining amount rem.
. Memoization reduces exponential tree to .
Unmemoized exponential explosion. Return if amount cannot be formed.