Minimum Coins asks for the fewest coins that add up to a target amount. For a canonical coin system (like US coins), a simple greedy rule works perfectly: always grab the biggest coin that still fits.
Problem. Given coin denominations coins and a target amount, return the minimum number of
coins that sum to amount. The system is canonical, meaning greedy is guaranteed optimal. Return
-1 if the amount cannot be made.
Example: coins = [25, 10, 5, 1], amount = 41 → answer 4 (because 25 + 10 + 5 + 1 = 41).
The slow way first
The general version of this problem (arbitrary coin systems) needs dynamic programming — an O(amount × coins) table — because greedy can fail. For coins = [1, 3, 4] and amount = 6, greedy takes 4 + 1 + 1 = 3 coins, but the best is 3 + 3 = 2.
The question to ask: is this coin system canonical? For US-style currency (and many others), the denominations are chosen so the greedy choice is always part of an optimal answer. When that holds, we can skip the whole DP table and just walk the coins once.
The idea: biggest coin first, every time
Sort the coins from largest to smallest. Walk through them. For each coin, take as many copies as fit into the remaining amount, subtracting and counting each time. Because the system is canonical, the largest fitting coin is never a mistake — so we never need to reconsider.
The key insight: greedy works only because the system is canonical. The same code on a non-canonical system can overshoot the optimal count.
Walk through it
Step through the animation. The coin pointer moves left to right across the sorted denominations. remaining shrinks as we subtract, and count ticks up with each coin taken. We take one 25, one 10, one 5, and one 1, ending with remaining = 0 and count = 4.
Pseudocode
sort coins from largest to smallest
count = 0
for each coin:
while coin fits in the remaining amount:
subtract coin from amount
count = count + 1
if amount is still greater than 0:
return -1 # cannot make exact change
return countThe Python solution
def min_coins(coins, amount):
coins.sort(reverse=True)
count = 0
for coin in coins:
while coin <= amount:
amount -= coin
count += 1
if amount > 0:
return -1
return countcoins.sort(reverse=True)puts the biggest denomination first so greedy can grab it.counttallies how many coins we have used so far.- The
forloop visits each denomination once, largest to smallest. - The
while coin <= amountloop takes as many copies of the current coin as fit, subtracting and counting each one. - After the loop, if
amount > 0the target was unmakeable (e.g. no penny in the system), so we return-1.
Complexity
| Case | Time | Notes |
|---|---|---|
| Greedy (canonical system) | O(n log n + amount / minCoin) (moderate) | sort, then subtract |
| DP (general systems) | O(amount × n) (moderate) | needed when greedy fails |
O(1) (fast)The sort is O(n log n) where n is the number of denominations (usually tiny). The subtraction loop runs once per coin taken. We use only a couple of counters, so O(1) extra space.
When this pattern shows up
Greedy means making the locally best choice and never looking back. It is fast and simple, but it is only correct when the problem has the right structure. For coin change, that structure is a canonical denomination system — confirm it before reaching for greedy.
Do not use greedy on arbitrary coin systems. For coins = [1, 3, 4] and amount = 6, greedy gives 3
coins but the optimum is 2. When the system is not canonical, fall back to the DP coin-change solution.
Practice
For coins = [25, 10, 5, 1] and amount = 41, after taking the 25 what is remaining, and which coin is tried next?
1. Why does greedy work for this problem?
2. Why do we sort the coins in descending order?
3. For coins = [1, 3, 4] and amount = 6, what does greedy return versus the optimum?
4. When should you return -1?