Python Program for the Coin Change Problem (Minimum Coins)
Intermediate Dynamic programming Time O(n ร k) Space O(n)
Finds the fewest coins that add up to an amount by solving every smaller amount first, or -1 when no mix of the coins makes it.
The coin change problem asks for the fewest coins that add up to an amount, using as many of each coin as you like. Paying the biggest coin that fits, again and again, works for most real coin sets but not for every set: with coins of 1, 3 and 4 it pays 6 as 4 + 1 + 1, three coins, when 3 + 3 needs only two. With coins of 2 and 5 it can't pay 6 at all: after a 5, the 1 left over can't be made, though 2 + 2 + 2 works. Dynamic programming finds the true fewest by working out every amount from 1 up, each one from the smaller amounts before it.
Program
def min_coins(coins, amount):
"""The fewest coins that add up to amount, or -1 if none do."""
# fewest[a]: the fewest coins for amount a (inf: no way yet)
fewest = [0] + [float("inf")] * amount
for a in range(1, amount + 1):
for coin in coins:
if coin <= a:
fewest[a] = min(fewest[a], fewest[a - coin] + 1)
if fewest[amount] == float("inf"):
return -1 # no mix of the coins makes this amount
return fewest[amount]
coins = [2, 5]
print("Fewest coins for 6:", min_coins(coins, 6))
print("Fewest coins for 3:", min_coins(coins, 3))
Open in the editor Download coin_change.py
Output
Fewest coins for 6: 3
Fewest coins for 3: -1
How it works
Step through the program line by line: each step below shows up as its line runs.
-
fewesthas one entry for each amount from 0 toamount. Amount 0 needs no coins; every other amount starts at infinity,float("inf"), meaning no way to pay it has been found yet. Infinity is larger than any number, somin()replaces it as soon as a real count turns up. - The amounts are worked out in order, from 1 up, trying each coin as the last one paid.
- If the coin fits, paying it leaves
a - coin, a smaller amount whose fewest coins are already known. That count plus this one coin is a way to paya, andfewest[a]keeps the smallest count found. Whena - coincan't be paid,inf + 1is still infinity, so it changes nothing. - If the amount is still at infinity at the end, no mix of the coins makes it, and the function returns -1. Otherwise it returns the count.
- With coins of 2 and 5, the program asks for 6, which takes three 2s, and for 3, which can't be made.
The list for 6, one amount at a time:
| Amount | A 2 last | A 5 last | Fewest |
|---|---|---|---|
| 0 | 0 | ||
| 1 | doesn't fit | doesn't fit | inf |
| 2 | fewest[0] + 1 = 1 | doesn't fit | 1 |
| 3 | fewest[1] + 1 = inf | doesn't fit | inf |
| 4 | fewest[2] + 1 = 2 | doesn't fit | 2 |
| 5 | fewest[3] + 1 = inf | fewest[0] + 1 = 1 | 1 |
| 6 | fewest[4] + 1 = 3 | fewest[1] + 1 = inf | 3 |
So 6 takes 3 coins: 2 + 2 + 2. The call for 3 builds the same list up to 3, where the entry is still infinity, so it returns -1.
Time and space complexity
| Time | O(n ร k) |
|---|---|
| Extra space | O(n) |
For an amount n and k coin values, the inner loop runs n ร k times in all, each a fixed amount of work, so the time is O(n ร k). The list holds n + 1 counts: O(n) extra space.
Other ways to write it
With recursion and lru_cache
Top-down: the fewest coins for an amount is one more than the fewest for what's left after the best last coin.
lru_cache, a decorator, remembers each amount's answer, so each is worked out once.
from functools import lru_cache
def min_coins(coins, amount):
@lru_cache(maxsize=None)
def fewest(a):
if a == 0:
return 0
best = float("inf")
for coin in coins:
if coin <= a:
best = min(best, fewest(a - coin) + 1)
return best
answer = fewest(amount)
return -1 if answer == float("inf") else answer
print(min_coins([1, 3, 4], 6), min_coins([2, 5], 3))Which coins, not only how many
A second list, last, records the coin that gave each amount its fewest count. Paying back from the amount, one
last coin at a time, lists the coins.
def coins_used(coins, amount):
fewest = [0] + [float("inf")] * amount
last = [0] * (amount + 1)
for a in range(1, amount + 1):
for coin in coins:
if coin <= a and fewest[a - coin] + 1 < fewest[a]:
fewest[a] = fewest[a - coin] + 1
last[a] = coin
if fewest[amount] == float("inf"):
return None
used = []
while amount > 0:
used.append(last[amount])
amount -= last[amount]
return used
print(coins_used([1, 3, 4], 6), coins_used([2, 5], 11))Try it yourself
- Use coins of 1, 3 and 4 for an amount of 6. The answer is 2 (3 + 3), where paying the biggest coin first would take 3.
- Pay 7 and 11 with coins of 2 and 5. They take 2 coins (5 + 2) and 4 coins (5 + 2 + 2 + 2).
- Try a real set of coins, such as British pence,
[1, 2, 5, 10, 20, 50], for 88p. It takes 6 coins. - Count the ways to make the amount instead of the fewest coins: start with
ways = [1] + [0] * amount, loop over the coins on the outside and the amounts on the inside, and addways[a - coin]toways[a]. Coins of 1, 2 and 5 make 5 in 4 ways.