Skip to content
Problem · Canonical Warm-Up
mediumunbounded-knapsack · dp-1d · bfsTime · O(amount * n)Space · O(amount)

Coin Change

Problem statement

Statement

You are given an array coins of distinct denominations and an integer amount. Return the fewest coins needed to make exactly that amount, assuming an unlimited supply of each denomination. If the amount cannot be formed, return -1.

Examples

coins = [1, 5, 11], amount = 15
-> 3                 (5 + 5 + 5; the greedy choice 11 + 1 + 1 + 1 + 1 needs 5)

coins = [2], amount = 3
-> -1                (every reachable amount is even)

coins = [1], amount = 0
-> 0                 (zero coins make zero)

coins = [1, 3, 4], amount = 6
-> 2                 (3 + 3; greedy takes 4 + 1 + 1 and needs 3)

Constraints

  • 1 <= len(coins) <= 12
  • 1 <= coins[i] <= 2^31 - 1, so a denomination can exceed the amount and must simply be ignored.
  • 0 <= amount <= 10^4
  • Order does not matter and coins may repeat, so this counts multisets rather than sequences.

What this problem is really testing

Whether you can tell that a greedy algorithm is wrong, and then say why. Everyone's instinct is to take the largest coin that fits and repeat, because that is how humans actually make change, and with real currency it happens to be optimal. That is an accident of design: the US and Euro denominations form what is called a canonical system, chosen so that greedy works. Hand yourself [1, 3, 4] and ask for 6 and greedy takes 4, then needs two 1s, for three coins, while the optimal answer is 3 + 3. The first thing to do in this interview is produce a counter-example like that one. It converts a hand-wave into a proof and it is the move the interviewer is waiting for.

Once greedy is dead, the structure is standard. The problem has optimal substructure, because an optimal way to make amount a using some coin c contains within it an optimal way to make a - c. If it did not, you could swap in the better sub-solution and improve the whole, which contradicts optimality. It also has overlapping subproblems, because a - c is reachable through many different coin orders. Those two properties together are the definition of a dynamic programming problem, and once you name them the recurrence writes itself.

The useful reframing is to stop thinking about coins and start thinking about a graph. Each amount from 0 to the target is a vertex, and each coin is an edge from a to a + c. Every edge has weight one, because using a coin costs one coin regardless of its denomination. "Fewest coins" is then literally "shortest path from 0 to amount" in an unweighted graph, and breadth-first search solves that by definition. The DP table and the BFS are the same computation visited in two different orders, which is a good thing to be able to say out loud.