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. 1.Coin Change — Minimum Coins
    medium
  2. 2.Coin Change II — Number of Combinations
    medium
  3. 3.Combination Sum IV — Ordered Ways
    medium
  4. 4.Perfect Squares
    medium
  5. 5.Minimum Cost to Fill Exact Weight
    medium
  6. 6.Minimum Coins With Given Denominations
    medium
  7. 7.Count Ways to Make an Amount
    medium
  8. 8.Rod Cutting — Unbounded Knapsack relation
    medium
  9. 9.Integer Break — related reusable-choice DP
    medium
  10. 10.Minimum Number of Coins to Reach Target
    medium

Also Important

8 advanced Coin Change variations worth practicing.

  1. 11.Coin Change With Limited Coins
    medium
  2. 12.Return Actual Coins Used
    medium
  3. 13.Coin Change With Impossible Amounts
    medium
  4. 14.Coin Change With Large Target
    hard
  5. 15.Canonical Coin Systems / Greedy vs DP
    medium
  6. 16.Count Ordered vs Unordered Ways
    medium
  7. 17.Bounded Coin Change — advanced
    hard
  8. 18.Coin Change Using BFS — alternative view
    medium

How to Think

For every amount a, evaluate using each available coin: 1 + dp[a - coin].

  1. Coins reusable unlimited times?Unbounded Knapsack
  2. Need minimum number of coins?dp[amount] = min coins (dp[0]=0, others INF)
  3. Need number of combinations?Coin Change II (Outer coin loop)
  4. Need order to matter (permutations)?Combination Sum IV (Outer amount loop)
  5. 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!

Visual Memory Rule
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 }