Coin Change (Greedy)

Given coin denominations and a target amount, make change using the fewest coins. The greedy approach repeatedly takes the largest coin not exceeding the remainder. This works for canonical systems like US coins but fails for arbitrary denominations — then you need DP. This lesson covers the greedy algorithm, when it is optimal, counterexamples, and implementations in C, Java, and Python with trace tables.

Overview

A cashier making change naturally picks the biggest coin first. That instinct is a greedy algorithm — fast and correct for many real currency systems, but not universally optimal.

US coins [25, 10, 5, 1] — amount 30:
  Take 25 → remainder 5
  Take 5  → remainder 0
  Answer: 2 coins ✓

Arbitrary coins [1, 3, 4] — amount 6:
  Greedy: 4 + 1 + 1 = 3 coins
  Optimal: 3 + 3 = 2 coins → greedy fails, use DP

Problem Statement

Input: Coin denominations coins[] (unlimited supply of each) and target amount amount.

Output: Minimum number of coins to sum exactly to amount, or indicate impossibility if no combination exists.

Greedy sub-problem: At each step, use as many of the largest coin ≤ remaining amount as possible.

Complexity (greedy): O(n) per amount if coins are pre-sorted, where n = number of denominations; O(n log n) if sorting first.

Greedy Algorithm

  1. Sort coins in decreasing order.
  2. Set remaining = amount and count = 0.
  3. For each coin c:
    • Take k = remaining // c coins of value c.
    • Add k to count; subtract k × c from remaining.
  4. If remaining == 0, return count; else no exact change (or greedy incomplete for non-canonical systems).
Key insight: Largest-first minimizes coin count only when the denomination system is canonical. Always verify before assuming greedy is optimal in interviews.

When Greedy Works

A coin system is canonical if the greedy algorithm produces the minimum number of coins for every amount.

  • US coins {1, 5, 10, 25} — canonical (greedy optimal).
  • Euro coins {1, 2, 5, 10, 20, 50, 100, 200} cents — canonical.
  • Binary / powers of two {1, 2, 4, 8, …} — greedy equals optimal.

Sufficient (not necessary) condition: if each coin is at least the sum of all smaller coins, greedy works. US coins satisfy a looser structural property that still guarantees optimality.

When Greedy Fails

Amount Coins Greedy result Optimal
6[1, 3, 4]4+1+1 → 3 coins3+3 → 2 coins
12[1, 4, 5]5+5+1+1 → 4 coins4+4+4 → 3 coins
15[1, 4, 6]6+6+1+1+1 → 5 coins6+4+4+1 → 4 coins
Interview trap: Never say “coin change is always greedy.” State the canonical assumption, give counterexample {1,3,4} for amount 6, then offer DP.

Code (C, Java, Python)

Keywords Types Functions Variables Strings Numbers Comments

Python

def coin_change_greedy(coins, amount):
    """Min coins via largest-first greedy. Optimal only if system is canonical."""
    coins = sorted(coins, reverse=True)
    count = 0
    remaining = amount
    used = []

    for coin in coins:
        if remaining <= 0:
            break
        k = remaining // coin
        if k:
            count += k
            remaining -= k * coin
            used.extend([coin] * k)

    if remaining != 0:
        return -1, []  # exact change impossible with these coins
    return count, used


if __name__ == "__main__":
    c, used = coin_change_greedy([25, 10, 5, 1], 41)
    print(f"count = {c}, coins = {used}")  # 4, [25, 10, 5, 1]

Java

import java.util.Arrays;

public class CoinChangeGreedy {

    /** Largest coin first — correct for canonical systems like US coins. */
    public static int minCoinsGreedy(int[] coins, int amount) {
        Integer[] sorted = Arrays.stream(coins).boxed().toArray(Integer[]::new);
        Arrays.sort(sorted, (a, b) -> b - a);

        int count = 0, remaining = amount;
        for (int coin : sorted) {
            if (remaining <= 0) break;
            int k = remaining / coin;
            count += k;
            remaining -= k * coin;
        }
        return remaining == 0 ? count : -1;
    }

    public static void main(String[] args) {
        int[] us = {25, 10, 5, 1};
        System.out.println(minCoinsGreedy(us, 41)); // 4
        System.out.println(minCoinsGreedy(new int[]{1, 3, 4}, 6)); // 3 (not optimal)
    }
}

C

#include <stdio.h>
#include <stdlib.h>

int cmp_desc(const void *a, const void *b) {
    return *(int *)b - *(int *)a;
}

int coin_change_greedy(int coins[], int n, int amount) {
    qsort(coins, n, sizeof(int), cmp_desc);
    int count = 0, i, remaining = amount;

    for (i = 0; i < n && remaining > 0; i++) {
        int k = remaining / coins[i];
        count += k;
        remaining -= k * coins[i];
    }
    return remaining == 0 ? count : -1;
}

int main(void) {
    int us[] = {25, 10, 5, 1};
    printf("%d\n", coin_change_greedy(us, 4, 41)); /* 4 */
    int bad[] = {1, 3, 4};
    printf("%d\n", coin_change_greedy(bad, 3, 6));  /* 3 — greedy, not optimal */
    return 0;
}

Output & Trace

US coins — amount 41, denominations [25, 10, 5, 1]

Step Coin Take (k) remaining count
Start410
1251161
210162
35113
41104 ← answer

Coins used: 25 + 10 + 5 + 1 = 41 with 4 coins (optimal for US system).

More examples

Coins Amount Greedy count Optimal?
[25, 10, 5, 1]302 (25+5)Yes
[25, 10, 5, 1]414Yes
[1, 3, 4]63 (4+1+1)No — optimal is 2
[1, 4, 5]124 (5+5+1+1)No — optimal is 3

Greedy vs DP (LeetCode 322)

LeetCode 322 — Coin Change asks for minimum coins with arbitrary denominations. Use DP, not greedy:

def coin_change_dp(coins, amount):
    """LeetCode 322 — min coins for any denomination set. O(n * amount)."""
    INF = amount + 1
    dp = [INF] * (amount + 1)
    dp[0] = 0

    for a in range(1, amount + 1):
        for coin in coins:
            if coin <= a:
                dp[a] = min(dp[a], dp[a - coin] + 1)

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

# coins=[1,3,4], amount=6 → 2 (not greedy's 3)

Rule of thumb: US/Euro-style currency → greedy is fine. Unknown or custom coins → DP.

Variants & Related Problems

Problem Goal Approach
Min coins (canonical)Fewest coinsGreedy largest-first
Min coins (general) — LC 322Fewest coinsDP (unbounded knapsack)
Count combinations — LC 518Number of waysDP
Exact change possible?Yes/noDP or BFS
Limited coin supplyBounded countsDP with multiplicity

Approach Comparison

Approach Time Space Notes
Greedy (largest first)O(n)O(1)Only if canonical — fast in practice
DP (min coins)O(n × amount)O(amount)Always correct for min coins
BFS on amountsO(amount × n)O(amount)Alternative for min coins
Brute forceExponentialO(amount)Not practical

Real-Life Applications

Domain Scenario Mapping
Cash registersReturn minimum coins to customerUS/Euro greedy change
Vending machinesMake exact change with stored coinsDenomination selection
Resource packingUse largest units first (sheets, rods)Same greedy pattern
Time breakdownExpress duration in hours, minutes, secondsGreedy on fixed unit sizes
Digital walletsSplit payment into standard bill amountsCanonical denomination systems
Game economiesConvert currency with fixed coin tiersGreedy if tiers are designed canonically

Interview Tips

  1. Ask: are denominations standard/canonical or arbitrary?
  2. State greedy: sort descending, take max coins of each denomination.
  3. Give counterexample coins=[1,3,4], amount=6 immediately when discussing failure.
  4. For LeetCode 322, write 1D DP: dp[a] = min(dp[a], dp[a-coin]+1).
  5. Mention unlimited vs bounded coin counts — changes the DP state.
  6. Link back to greedy introduction and DP introduction for the full picture.

Key Takeaway

Coin change greedy = take as many of the largest coin as possible, repeat. Works for canonical systems (US, Euro). For general coins, use DP (LeetCode 322). Always know when greedy fails.

Track complete: Greedy introduction · Dynamic programming · Algorithms Hub