Coin Change Problems

Coin change is a classic unbounded knapsack pattern: each coin can be used unlimited times. This lesson covers minimum coins (LeetCode 322), number of combinations (518), greedy vs DP, and full solutions in C, Java, and Python with output trace tables.

Overview

Given coin denominations and a target amount, common questions are:

  • Minimum coins — fewest coins to make exact amount (or −1 if impossible)
  • Count combinations — how many distinct multisets of coins sum to amount
  • Count permutations — order matters (different loop order in DP)
Problem Goal State LeetCode
Minimum coinsMin countdp[a] = min coins for amount a322
CombinationsCount ways (order ignored)dp[a] += ways using coins ≤ last518
PermutationsCount orderingsOuter loop on amount377
Exact sum (0/1)Each coin once2D knapsackSubset sum variants
Unbounded coin DP pattern:
  dp[0] = base case (0 coins or 1 way)
  for each amount a from 1 .. target:
      for each coin c:
          use dp[a - c] to update dp[a]

Greedy vs Dynamic Programming

Greedy (always pick largest coin) works for canonical systems like US coins {1, 5, 10, 25} but fails for arbitrary denominations.

Input Greedy result Optimal (DP) Explanation
coins=[1,5,10,25], amount=306 coins (25+5)2 coins (25+5)Greedy works ✓
coins=[1,3,4], amount=63 coins (4+1+1)2 coins (3+3)Greedy fails ✗ — need DP
coins=[2], amount=3Impossible−1Odd amount with even coins
Interview rule: Mention greedy first if asked, then show a counterexample and switch to DP unless the problem guarantees canonical coins.

Coin Change I — Minimum Coins

Problem: Given coins and amount, return the fewest coins needed, or −1 if impossible.

Recurrence: dp[a] = min(dp[a], dp[a − c] + 1) for each coin c ≤ a

Base: dp[0] = 0; initialize other cells to ∞ (or amount + 1)

Minimum Coins Code (C, Java, Python)

Keywords Types Functions Variables Strings Numbers Comments

Python

def coin_change(coins, amount):
    """LeetCode 322 — minimum coins for exact amount. O(amount × len(coins))."""
    INF = amount + 1
    dp = [INF] * (amount + 1)
    dp[0] = 0  # zero coins needed for amount 0

    for a in range(1, amount + 1):
        for c in coins:
            if c <= a:
                # try using coin c: one coin + best for remainder
                dp[a] = min(dp[a], dp[a - c] + 1)

    return dp[amount] if dp[amount] != INF else -1


if __name__ == "__main__":
    tests = [([1, 2, 5], 11), ([2], 3), ([1, 3, 4], 6)]
    for coins, amount in tests:
        print(f"coins={coins}, amount={amount} → {coin_change(coins, amount)}")

Java

public class CoinChangeMin {

    /** Minimum coins — bottom-up unbounded knapsack style. */
    public static int coinChange(int[] coins, int amount) {
        int INF = amount + 1;
        int[] dp = new int[amount + 1];
        for (int i = 1; i <= amount; i++) dp[i] = INF;
        dp[0] = 0;

        for (int a = 1; a <= amount; a++) {
            for (int c : coins) {
                if (c <= a) {
                    dp[a] = Math.min(dp[a], dp[a - c] + 1);
                }
            }
        }
        return dp[amount] == INF ? -1 : dp[amount];
    }

    public static void main(String[] args) {
        System.out.println(coinChange(new int[]{1, 2, 5}, 11));  // 3
        System.out.println(coinChange(new int[]{2}, 3));           // -1
        System.out.println(coinChange(new int[]{1, 3, 4}, 6));     // 2
    }
}

C

#include <stdio.h>

#define MAX_AMT 1000

/* Minimum coins for amount; returns -1 if impossible */
int coin_change_min(int coins[], int n, int amount) {
    int dp[MAX_AMT + 1];
    int INF = amount + 1;
    int a, i, c;

    for (a = 0; a <= amount; a++) dp[a] = INF;
    dp[0] = 0;

    for (a = 1; a <= amount; a++) {
        for (i = 0; i < n; i++) {
            c = coins[i];
            if (c <= a && dp[a - c] + 1 < dp[a]) {
                dp[a] = dp[a - c] + 1;
            }
        }
    }
    return dp[amount] == INF ? -1 : dp[amount];
}

int main(void) {
    int c1[] = {1, 2, 5}, c2[] = {2}, c3[] = {1, 3, 4};
    printf("%d\n", coin_change_min(c1, 3, 11)); /* 3 */
    printf("%d\n", coin_change_min(c2, 1, 3));  /* -1 */
    printf("%d\n", coin_change_min(c3, 3, 6));  /* 2 */
    return 0;
}

Minimum Coins Output & Trace

Program output (all three languages)

Language Input Console Output Explanation
Python[1,2,5], 11→ 35+5+1 = three coins
Python[2], 3→ -1Cannot make odd 3 with only 2s
Python[1,3,4], 6→ 23+3 beats greedy 4+1+1
Java / Csame tests3
-1
2
Identical logic; C prints one per line

DP trace — coins = [1, 2, 5], amount = 11

Amount a dp[a] (min coins) Best choice Explanation
00Base case
111One coin of 1
212One coin of 2
322+1dp[2]+1
422+2Two coins of 2
515Single coin 5
625+1dp[5]+1
1025+5Two coins of 5
1135+5+1dp[10]+1 → answer 3

Coin Change II — Combinations

Problem: Count how many combinations of coins sum to amount (order does not matter).

Key: Loop over coins outer, amount inner to avoid counting permutations twice.

Recurrence: dp[a] += dp[a − c] for each coin c

Combinations Code (C, Java, Python)

Python

def coin_change_combinations(coins, amount):
    """LeetCode 518 — count combinations (order ignored)."""
    dp = [0] * (amount + 1)
    dp[0] = 1  # one way to make amount 0: use no coins

    for c in coins:                    # outer: each coin type once
        for a in range(c, amount + 1):
            dp[a] += dp[a - c]         # add ways that use coin c

    return dp[amount]


if __name__ == "__main__":
    print(coin_change_combinations([1, 2, 5], 5))   # 4
    print(coin_change_combinations([1, 2, 5], 11))  # 11

Java

public class CoinChangeCombinations {

    public static int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;

        for (int c : coins) {
            for (int a = c; a <= amount; a++) {
                dp[a] += dp[a - c];
            }
        }
        return dp[amount];
    }

    public static void main(String[] args) {
        int[] coins = {1, 2, 5};
        System.out.println(change(5, coins));   // 4
        System.out.println(change(11, coins));  // 11
    }
}

C

#include <stdio.h>

#define MAX_AMT 1000

int coin_change_ways(int coins[], int n, int amount) {
    int dp[MAX_AMT + 1] = {0};
    int i, a, c;

    dp[0] = 1;

    for (i = 0; i < n; i++) {
        c = coins[i];
        for (a = c; a <= amount; a++) {
            dp[a] += dp[a - c];
        }
    }
    return dp[amount];
}

int main(void) {
    int coins[] = {1, 2, 5};
    printf("%d\n", coin_change_ways(coins, 3, 5));   /* 4 */
    printf("%d\n", coin_change_ways(coins, 3, 11));  /* 11 */
    return 0;
}

Combinations Output & Trace

Program output

Language Input Output Valid combinations (amount=5)
Python[1,2,5], 545 | 2+2+1 | 2+1+1+1 | 1×5
Python[1,2,5], 1111Eleven distinct multisets
Java / C[1,2,5], 54Same as Python
Java / C[1,2,5], 1111Full DP table fill

DP trace — coins = [1, 2, 5], amount = 5 (combinations)

After processing dp[0..5] Explanation
Init[1,0,0,0,0,0]One way to make 0
Coin 1[1,1,1,1,1,1]+1 way per amount using 1s
Coin 2[1,1,2,2,3,3]dp[2]+=dp[0], dp[3]+=dp[1], …
Coin 5[1,1,2,2,3,4]dp[5]+=dp[0] → 4 ways total
Loop order matters: Amount outer + coins inner counts permutations. Coins outer + amount inner counts combinations.

Unbounded Knapsack Connection

Coin change is the unbounded subset-sum variant: unlimited copies of each item (coin).

Variant Reuse items? Inner loop direction Example
0/1 KnapsackEach item onceAmount descendingSubset sum with unique coins
UnboundedUnlimited copiesAmount ascendingCoin change, rod cutting
BoundedLimited copiesBinary split / 2D DPEach coin has a count

More Examples

Example 1: Impossible amount

coinsamountMin coinsCombinationsWhy
[2, 4]7−10All sums are even

Example 2: Greedy failure (again)

coinsamountGreedyDP minBest combo
[1, 3, 4]63 (4+1+1)23+3

Example 3: Complexity summary

ProblemTimeSpaceNotes
Min coinsO(amount × coins)O(amount)Pseudo-polynomial
CombinationsO(amount × coins)O(amount)Watch loop order
Memoization (min)O(amount × coins)O(amount) + stackTop-down alternative

Approach Comparison

Approach Time Space When to use
GreedyO(n log n)O(1)Canonical coin systems only
Bottom-up DPO(A × C)O(A)Standard interview solution
Top-down memoO(A × C)O(A) + stackQuick recursive draft
BFS (min coins)O(A × C)O(A)Alternative; less common

A = amount, C = number of coin types, n = number of coins sorted for greedy.

Real-Life Applications

Coin change models any situation where you must reach an exact total using a fixed set of denominations—minimizing pieces or counting distinct ways.

Domain Real scenario Coin change mapping Variant
Retail / POSCash register making change for a customerCoins/bills = denominations; amount = change due; goal = fewest coinsMinimum coins
Vending machinesAccepting payment and returning exact changeAvailable coin tubes = limited supply; amount = refundMin coins + inventory
Banking / ATMsDispensing cash in standard note/coin mixesDenominations = {100, 50, 20…}; amount = withdrawalMin coins (often greedy works for USD/EUR)
Currency exchangeBreaking foreign notes into local coins at a kioskNon-standard denominations → DP required (greedy fails)Minimum coins
AccountingPaying an invoice using exact bill/cheque combinationsDenominations = payment options; amount = invoice totalCount combinations
Games / appsCrafting items from resource bundles (e.g. 3 wood + 2 stone)Resources = “coins”; target = recipe cost; count valid recipesCombinations
Music / rhythmFilling a bar with note lengths that sum to exact beatsNote values = denominations; bar length = amountCount combinations
Public transitAdding fare credit using top-up incrementsTop-up amounts = coins; target = exact fare balanceMinimum steps
Why greedy fails in the wild: US coins {1, 5, 10, 25} are canonical—greedy works. Many custom token systems (loyalty points, in-game currency, foreign coins) are not. Production systems often run DP or precomputed lookup tables for non-canonical sets.

Interview Tips

  1. Clarify: minimum coins, count combinations, or count permutations?
  2. State dp[a] meaning before writing loops.
  3. Mention pseudo-polynomial time O(amount × coins).
  4. For combinations vs permutations, explain outer/inner loop order.
  5. Give greedy counterexample [1,3,4], amount=6.
  6. Return −1 when min coins impossible; 0 combinations when amount unreachable.
  7. Connect to knapsack problems (unbounded variant).

Key Takeaway

Coin change is unbounded knapsack on amount: fill dp[0…amount], each coin updates reachable sums. Minimum coins uses min; counting uses += with careful loop order for combinations vs permutations.

Next up: Knapsack problems · Fibonacci & climbing stairs · Memoization vs tabulation