Coin Change
Finds the fewest coins for an amount with DP, and shows when the greedy approach goes wrong.
Worth knowing first
#How does it work?
The coin change problem asks for the fewest coins that pay an amount with the given denominations. Each coin can be used any number of times.
Greedy (always take the largest coin that fits) is right for real currencies like the euro, but not for every coin system. With coins 1, 3 and 4, greedy pays 6 as 4 + 1 + 1 = 3 coins, while 3 + 3 = 2 coins is enough.
DP solves every amount in turn: dp[a] = 1 + min(dp[a − c]), i.e. if the last coin is c, add one coin to the best solution for the remaining a − c. Keeping the chosen coin in last[a] lets you walk back to the coins themselves.
#Try it yourself
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | |
|---|---|---|---|---|---|---|---|
| dp | 0 | ||||||
| last |
The coins are 1, 3 and 4. How few coins can pay an amount? Try greedy first, then DP, for 6.
#Step by step
- 01dp[0] = 0.
- 02For each a = 1 … A and each coin c ≤ a, compute dp[a − c] + 1.
- 03Write the smallest to dp[a] and the coin that gave it to last[a].
- 04Start at A and subtract last[a] repeatedly to list the coins used.
#Example code
The same algorithm in 8 languages. Every program is compiled, run and checked to print the same output.
COINS = [1, 3, 4]
INF = float("inf")
def min_coins(amount: int):
"""Returns (fewest coins, the coins used)."""
dp = [0] + [INF] * amount
last = [0] * (amount + 1) # the coin that gave dp[a]
for a in range(1, amount + 1):
for c in COINS:
if c <= a and dp[a - c] + 1 < dp[a]:
dp[a] = dp[a - c] + 1
last[a] = c
used, a = [], amount
while a > 0:
used.append(last[a])
a -= last[a]
return dp[amount], used
def greedy(amount: int) -> int:
"""Largest coin first: fast, but not always the fewest."""
count = 0
for c in sorted(COINS, reverse=True):
count += amount // c
amount %= c
return count
count, used = min_coins(6)
print(count)
print(" ".join(map(str, used))) # 3 + 3
print(greedy(6)) # 4 + 1 + 1: one coin more#Complexity
| Operation | Time |
|---|---|
| Time | O(A · k) |
| Memory | O(A) |
| Greedy | O(A) or O(k log k), but not always right |
| Number of ways to pay (same table) | O(A · k) |
A is the amount and k the number of denominations. Like knapsack it is pseudo-polynomial: the table grows with the amount. With the loops reordered, the same table also answers "in how many different ways can we pay?".
#When to use it?
Making change at tills and ATMs, reaching a score in a game with the fewest moves, and completing a target as efficiently as possible from units of resources. It is also a good example of when greedy is and isn't enough.
#Practice
Log in to answer questions and save your progress.
Easy
What does dp[a] hold?
- 1The fewest coins needed to pay amount a
- 2How many coins are worth a
- 3The amount left
- 4The number of ways to pay a
With coins 1, 3 and 4, how many coins does greedy use for 6?
- 13
- 22
- 34
- 46
What is this DP's time complexity (A amount, k denominations)?
- 1O(A · k)
- 2O(2ᴬ)
- 3O(A + k)
- 4O(k²)
Medium
What is the transition?
- 1dp[a] = 1 + min(dp[a − c]) over every c ≤ a
- 2dp[a] = dp[a − 1] + 1
- 3dp[a] = max(dp[a − c]) + c
- 4dp[a] = dp[a / c]
Hard
When does greedy always give the fewest coins?
- 1When the coin system is "canonical" (e.g. 1, 2, 5, 10, 20, 50)
- 2Never
- 3When the coins are primes
- 4When the amount is below the sum of the coins