Pattern #74
Coin Change
Reusable coin denomination minimization and combination counting. Master why greedy choices fail, why dp[0] = 0 and others initialize to INF, and how loop ordering controls combinations vs permutations.
Must Solve
10 core questions — solve these first.
- 1.Coin Change — Minimum CoinsUnbounded min coins DP: dp[a] = min(dp[a], 1 + dp[a-coin]) with INF basemedium
- 2.Coin Change II — Number of CombinationsUnbounded combinations DP: outer coin loop, inner amount left-to-rightmedium
- 3.Combination Sum IV — Ordered WaysUnbounded permutations DP: outer amount loop, inner coin loopmedium
- 4.Perfect SquaresUnbounded min items DP: coins are perfect squares 1, 4, 9...medium
- 5.Minimum Cost to Fill Exact WeightUnbounded min cost DP with exact weight capacity targetmedium
- 6.Minimum Coins With Given DenominationsBase Coin Change min coins DP initialized with INFmedium
- 7.Count Ways to Make an AmountUnbounded combination counting DPmedium
- 8.Rod Cutting — Unbounded Knapsack relationUnbounded max profit DP: piece length = weight, selling price = valuemedium
- 9.Integer Break — related reusable-choice DPUnbounded product maximization DP for integer split partsmedium
- 10.Minimum Number of Coins to Reach TargetMin coins DP returning -1 if dp[target] == INFmedium
Also Important
8 advanced Coin Change variations worth practicing.
- 11.Coin Change With Limited CoinsBounded Knapsack DP with coin count limits (binary splitting)medium
- 12.Return Actual Coins UsedTrack choice[a] pointers during DP fill to reconstruct optimal coin listmedium
- 13.Coin Change With Impossible AmountsDetecting unreachable amounts where dp[target] remains INFmedium
- 14.Coin Change With Large TargetAnalyzing constraint scaling when amount = 10^9hard
- 15.Canonical Coin Systems / Greedy vs DPProof of why Greedy fails on arbitrary coin systems (e.g. [1, 3, 4] for amount 6)medium
- 16.Count Ordered vs Unordered WaysLoop order comparison: outer coin (unordered) vs outer amount (ordered)medium
- 17.Bounded Coin Change — advancedDeque / Sliding window max optimization for bounded coin quantitieshard
- 18.Coin Change Using BFS — alternative viewShortest path BFS tree over amount states when all edges cost 1 movemedium
How to Think
For every amount a, evaluate using each available coin: 1 + dp[a - coin].
- Coins reusable unlimited times?Unbounded Knapsack
- Need minimum number of coins?dp[amount] = min coins (dp[0]=0, others INF)
- Need number of combinations?Coin Change II (Outer coin loop)
- Need order to matter (permutations)?Combination Sum IV (Outer amount loop)
- Is largest coin first always optimal?NO! Greedy fails on arbitrary coin systems
Go Quick Reference — Coin Change
Compilable Go code demonstrating Coin Change Minimum Coins (INF base) and Coin Change II Combinations Count.
package main
import "fmt"
// Coin Change Min Coins: O(Amount * Coins) time, O(Amount) space
func coinChange(coins []int, amount int) int {
const inf = 1e9
dp := make([]int, amount+1)
for a := 1; a <= amount; a++ {
dp[a] = inf // Initialize unreachable amounts to INF
}
dp[0] = 0 // Base case: amount 0 needs 0 coins
for a := 1; a <= amount; a++ {
for _, coin := range coins {
if coin <= a && dp[a-coin] != inf {
if 1+dp[a-coin] < dp[a] {
dp[a] = 1 + dp[a-coin]
}
}
}
}
if dp[amount] == inf {
return -1
}
return dp[amount]
}
// Coin Change II Combinations Count: Outer Coin Loop
func change(amount int, coins []int) int {
dp := make([]int, amount+1)
dp[0] = 1 // Base case: 1 way to make amount 0 (empty set)
for _, coin := range coins {
for a := coin; a <= amount; a++ {
dp[a] += dp[a-coin]
}
}
return dp[amount]
}
func main() {
coins := []int{1, 3, 4}
amount := 6
fmt.Println("Min Coins:", coinChange(coins, amount)) // Output: 2 (Coins: 3+3)
fmt.Println("Combinations Count:", change(5, []int{1, 2, 5})) // Output: 4
}👉 Coin Change: O(Amount × Coins) time and O(Amount) 1D array space.
Core Coin Mechanics & Greedy Failure Proof
Why Base Case dp[0] = 0 & INF
• dp[0] = 0: Amount 0 requires zero coins.
• All positive amounts start at INF. Initializing to 0 would incorrectly imply positive amounts can be formed for free!
Greedy Failure Proof
• Coins: [1, 3, 4], Amount: 6.
• Greedy: Pick largest 4 -> remaining 2 -> Pick 1, Pick 1 = 3 coins (4+1+1).
• Optimal DP: Pick 3 -> remaining 3 -> Pick 3 = 2 coins (3+3). Greedy fails!
Coin Change Goal -> Minimum Coins to reach Target Amount
Base Case -> dp[0] = 0; All other amounts = INF
Transition -> dp[a] = min(dp[a], 1 + dp[a - coin])
Greedy Failure -> [1, 3, 4] for amount 6 -> Greedy yields 3 coins (4+1+1) vs DP yields 2 coins (3+3)
Combinations Count (Coin Change II) -> for coin in coins { for amount in coin..W }
Permutations Count (Combination Sum IV) -> for amount in 1..W { for coin in coins }Coin Change Variants & Shortest-Path BFS View
Combinations vs Permutations Loop Order
• Coin Change II (Combinations): Outer coin loop (for c in coins { for a in c..W }). Order ignored ({1,2} == {2,1}).
• Combination Sum IV (Permutations): Outer amount loop (for a in 1..W { for c in coins }). Order matters ({1,2} != {2,1}).
Shortest Path BFS Alternative View
• Think of amounts as graph nodes; using coin c creates a directed edge of weight 1 from node x to x + c.
• BFS finds minimum coins as the unweighted shortest path from 0 to target amount.
Reconstructing Optimal Coins List
To recover actual coins used in 1D Coin Change:
Maintain an array choice[a] recording which coin denomination improved dp[a].
Backtrack from a = amount: record choice[a], then set a -= choice[a] until a == 0.
Common Interview Mistakes
- • Assuming Greedy (picking largest coin first) is always optimal for arbitrary coin systems.
- • Initializing positive amounts to 0 for minimum coins DP instead of infinity (INF).
- • Forgetting base case initialization dp[0] = 0, causing all amounts to evaluate as unreachable.
- • Adding 1 to INF (dp[a-coin] + 1) without checking if dp[a-coin] == INF, causing integer overflow.
- • Treating coin denominations as 0/1 items (single use) instead of Unbounded items (unlimited reuse).
- • Swapping loop order in Coin Change II (accidentally counting permutations instead of combinations).
- • Failing to return -1 when dp[target] remains INF (unreachable target amount).
- • Attempting 1D capacity DP when target amount = 10^9 without checking constraint bounds (Memory Limit Exceeded).
- • Confusing Coin Change (min coins DP) with Coin Change II (combinations count DP).
Interview Rules
Reusable coins + target amount? → Coin Change (Unbounded Knapsack)
Rule 1 → Define dp[a] = min coins to make amount a
Rule 2 → Base Case: dp[0] = 0; All other amounts = INF
Rule 3 → Transition: dp[a] = min(dp[a], 1 + dp[a-coin])
Rule 4 → Return -1 if dp[target] == INF
Rule 5 → Coin Change II = Outer coin loop; Combination Sum IV = Outer amount loop
Rule 6 → Greedy fails on non-canonical coins (e.g. [1, 3, 4] for amount 6)
Time Complexity → O(Amount * Coins)
Space Complexity → O(Amount) 1D Array💡 Golden Rule: For every amount, try using each reusable coin once now, then add one to the best answer already known for the remaining amount.
Production Thinking
Payment Denomination Composition → Coin Change calculates minimum bill/coin counts for cash dispense machines.
Package Size Minimization → minimum coins DP calculates smallest number of standard shipping boxes required to pack an order.
Cloud Resource Allocation → unbounded min DP computes fewest instance bundles needed to satisfy required compute capacity.
Production warning → check target amount scaling; massive amounts (amount > 10⁶) will trigger Memory Limit Exceeded crashes without state compression.
Remember This
Coin Change Goal: Minimum coins to reach amount
Base Case: dp[0] = 0, others INF
Transition: dp[a] = min(dp[a], 1 + dp[a-coin])
Greedy Failure: [1, 3, 4] for amount 6 -> DP gives 2 (3+3) vs Greedy gives 3 (4+1+1)
Unreachable Target: return -1 if dp[target] == INF
Combinations Count: for coin { for amount }
Permutations Count: for amount { for coin }