Cairn
algorithms · greedy · O(d log d)

back to Greedy

Coin Change

Given a set of coin denominations — an unlimited supply of each — and a target amount, find the fewest coins that sum to it exactly. This is the classic change-making problem. The greedy idea is the one a cashier already uses without thinking about it: take as many of the current-largest denomination as fit without going over what's left, then move to the next-smaller denomination, and repeat until the amount hits zero — no lookahead, no reconsidering an earlier choice. This is the site's fourth entry filed under Greedy, after Huffman Coding, Activity Selection, and Fractional Knapsack — and the first one where whether the rule is even correct depends on the specific denominations handed to it, not just on the shape of the problem.

On familiar currency — {1, 5, 10, 25}, U.S. pennies/nickels/dimes/quarters — greedy is provably optimal for every amount (see Why it works). Change the denominations even slightly and that guarantee disappears entirely: a set of denominations where greedy is always optimal is called canonical; one where it isn't, isn't. Whether a given set is canonical isn't obvious from looking at it — see Pitfalls for two denomination sets, each differing from U.S. currency by a single coin, where greedy gets the wrong answer, one of them so badly it finds no answer at all.

Try it

Enter comma-separated denominations and a target amount, press Load, then Step or Run. Each step takes as many of the current-largest remaining denomination as fit, or skips it if none do. The bar fills left to right as coins are taken; each chip below it tracks how many of that denomination have been used so far. The demo also computes the true minimum via dynamic programming for comparison (capped at amount 5000 so that stays instant) — so a greedy answer that falls short, or fails outright, is visible immediately, not just claimed.

amount filled

Press Load.

Why it works

Whether this greedy rule is even correct depends entirely on which denominations are on the table — a genuinely different situation from every other greedy entry on this site, where the rule's correctness is a property of the problem, not of the specific input data. For the U.S. coin system {1, 5, 10, 25} used as this page's default, though, it's provably optimal, and the proof is a sequence of local exchanges rather than one global argument.

Take any optimal way to make some amount N out of pennies, nickels, dimes, and quarters, and look for an improving swap:

None of these swaps can ever make a solution worse, so any optimal solution that doesn't already satisfy all three bounds isn't actually optimal — a contradiction. That pins pennies to at most 4, nickels to at most 1, and dimes to at most 2, which together cover at most 4 + 5 + 20 = 29 cents without a single quarter — so any amount needing more than that forces at least one quarter too, and the same kind of swap argument (trade coins worth 25¢ for one quarter whenever that's possible without breaking the bounds above) forces the quarter count as high as it can go without overshooting N. Pin all four counts down this way and there's only one combination left standing — and it's exactly the one greedy computes, largest denomination first. Finishing the last step in full generality (why the forced quarter count is exactly floor(N/25) for every possible remainder, not one less) is routine casework this page won't spell out line by line; instead, it's checked directly — greedy's coin count matches a from-scratch dynamic-programming optimum for every amount from 0 to 20,000, not just asserted to.

None of the three swap arguments above are specific to the abstract numbers 1, 5, 10, 25 — they work because each larger denomination happens to be reachable, cheaply, from a small combination of smaller ones in this specific system (5 pennies, 2 nickels, 3 dimes-worth). Change the denominations and that stops being true — see Pitfalls.

Reference implementation

function greedyChange(denominations, amount) {
  const sorted = [...denominations].sort((a, b) => b - a);

  let remaining = amount;
  const used = [];
  for (const c of sorted) {
    const count = Math.floor(remaining / c);
    if (count > 0) {
      used.push({ value: c, count });
      remaining -= count * c;
    }
  }
  return { used, remaining }; // remaining > 0 means greedy couldn't finish
}

Pitfalls

Non-canonical denominations: greedy finds a solution, just not the best one. Coins {1, 15, 25}, amount 30: sorted largest first, greedy takes one 25 (5 left), 15 doesn't fit into 5, so it falls back to five 1s — 6 coins total. The true optimum, confirmed by the demo's own dynamic-programming check, is two 15s: 2 coins. A quarter is the single most valuable coin at or below 30, so greedy commits to it immediately — a commitment that burns exactly the 5 a second 15-coin would have needed instead, and greedy never reconsiders a choice once made.

Non-canonical denominations: greedy can fail completely, even when a solution exists. Coins {3, 5}, amount 11: sorted largest first, greedy takes two 5s (10), leaving 1 — and no coin in {3, 5} is ≤ 1, so it gets stuck with a nonzero remainder and no solution. But 3 + 3 + 5 = 11 is a real, valid solution (3 coins) that greedy never finds, because committing to two 5s forecloses it. This is a strictly worse failure than the previous one: not just non-minimal, but wrong in the strongest sense — reporting "impossible" when it isn't. Contrast with coins {5, 10}, amount 4: greedy also gets stuck there, but that amount genuinely can't be made from those denominations at all (the demo's own DP check agrees: no solution exists). "Greedy got stuck" alone doesn't say which of these happened, so the demo always cross-checks against its DP result before choosing which of the two messages to show, rather than treating every stuck state as the same kind of failure.

Whether greedy fails isn't a property you can check with one example amount. The same coins {1, 3, 4}: amount 6 fails (greedy takes 4 + 1 + 1 = 3 coins, the optimum is 3 + 3 = 2), but amount 8 with the identical denominations succeeds (greedy takes 4 + 4 = 2 coins, matching the optimum exactly). Trying one amount and generalizing to "this coin system is fine" or "this coin system is broken" either way is exactly the mistake this pair guards against — whether a set of denominations is canonical is a property of the whole system across every amount, not something a single successful or failed example settles. Determining canonicity in general is itself a real, solved-but-nontrivial algorithmic question that this page doesn't implement; the demo's live DP comparison is the practical stand-in — for the exact input in front of you, it always tells you whether greedy matched, instance by instance, rather than claiming to know in advance.

Complexity

Time: O(d log d), where d is the number of distinct denominations — entirely the cost of the initial sort. The pass after that is O(d), one floor-division per denomination, independent of the amount's size: there's no per-unit loop, which is why the demo doesn't slow down at amount 5000 versus amount 40. Space: O(d) for the sorted list and the used breakdown. This demo's own dynamic-programming optimal-count comparison, shown purely for contrast, is O(amount × d) and capped at amount 5000 for exactly that reason — the same caveat pattern as Activity Selection's brute-force cap, nothing the greedy algorithm itself needs.

See Choosing a Greedy Strategy for how this entry compares against the site's other nine Greedy entries — short version: this is the category's one Tier 2 entry, exact only for canonical denomination sets, with no way to tell canonicity from the numbers alone.