Coin Change Problem
- Why a greedy approach fails on arbitrary coin sets
- How to identify optimal substructure and overlapping subproblems
- How to derive the Bellman recurrence equation from scratch
- How to trace a DP table step by step on a concrete example
- Time and space complexity analysis, and how DP compares to brute force
Prerequisites
Before studying this problem, you should be comfortable with:
| Topic | Why It Matters |
|---|---|
| Recursion | Understanding the brute-force approach and its cost |
| Arrays | Building and indexing the dp[] table |
| Big-O Notation | Comparing brute force vs. DP efficiency |
| Basic combinatorics | Reasoning about "combinations of coins" |
The Problem (Intuitively)
Imagine you are a cashier and need to give a customer $6 in change using coins worth $1, $3, and $4. What is the fewest coins you can use?
This question—trivial in familiar monetary systems—turns out to be surprisingly hard to solve correctly for arbitrary sets of coin denominations. A quick guess might give you the wrong answer.

The Mental Trap
Most people's brains are wired to be Greedy. In a split second, you might think:
- "Okay, give them the biggest coin first: a $4 coin."
- "I still owe them $2. I'll give them two $1 coins."
- Total: 3 coins. ($4 + $1 + $1)
Wait! If you stop for a second and look closer, you'll see a better way:
- Two $3 coins also make $6.
- Total: 2 coins. ($3 + $3)
This gap between the Greedy choice (3 coins) and the Optimal choice (2 coins) is exactly why this problem is famous. In familiar systems like Euros or US Dollars, the greedy choice is always optimal. But for arbitrary sets of coins, we need Computational Intelligence to find the truth.
Formal Problem Statement
Given:
- A set of distinct coin denominations
- A target amount
- An infinite supply of each coin denomination
Objective: Find the minimum number of coins that sum exactly to .
If no valid combination exists, the algorithm must return .
Historical Context
The Coin Change Problem is a classic in combinatorial optimization. It is a special case of the unbounded knapsack problem and gained mathematical rigor with the development of Dynamic Programming by Richard Bellman in the 1950s.
"The 1950s were not good years for mathematical research... I used the word 'programming' to hide what I was actually doing, which was mathematical research."
— Richard Bellman
- Reference: Bellman, R. (1957). Dynamic Programming. Princeton University Press.
- Further reading: Dynamic Programming (Wikipedia)
Brute Force: Why Is It Slow?
The most natural approach is to recursively try every possible coin at each step.
Pseudocode (recursive brute force):
function minCoins(coins, amount):
if amount == 0: return 0
if amount < 0: return ∞
best = ∞
for each coin c in coins:
result = minCoins(coins, amount - c)
best = min(best, result + 1)
return best
Each call branches into sub-calls. The recursion tree has depth up to (when the smallest coin has value 1), giving:
This exponential time complexity is completely impractical for even moderate values of . The root cause is that the same sub-amounts are recomputed countless times — this is exactly where Dynamic Programming helps.
Why Greedy Fails
A common first instinct is the greedy strategy: always pick the largest coin denomination that fits in the remaining amount. While this works for canonical systems like US Dollars or Euros, it fails for arbitrary denominations.
Counterexample — , :
| Strategy | Coins Used | Total Coins |
|---|---|---|
| Greedy | 3 | |
| Optimal | 2 ✓ |
Greedy commits to a local choice without considering future implications. It never "looks back" to try a different coin that leads to a better global solution. We must explore the full solution space — efficiently, with DP.
Dynamic Programming Solution
Two Key DP Properties
A problem is solvable with DP when it has both:
- Optimal Substructure: The optimal solution to amount is built from optimal solutions to smaller amounts .
- Overlapping Subproblems: The same sub-amounts appear repeatedly in the brute-force recursion tree — caching them eliminates redundant work.
State Definition
Base Case and Initialization
Recurrence (Bellman Equation)
For each amount from to , and for each coin with :
In words: "to reach amount , try adding one coin on top of the best solution for amount ."
Pseudocode (Bottom-Up Tabulation)
function coinChange(coins, S):
dp = array of size (S + 1), initialized to ∞
dp[0] = 0
for i from 1 to S:
for each coin c in coins:
if c ≤ i:
dp[i] = min(dp[i], dp[i - c] + 1)
return dp[S] if dp[S] ≠ ∞ else -1
Step-by-Step Table Trace
Input: ,
| Best via coin 1 | Best via coin 3 | Best via coin 4 | ||
|---|---|---|---|---|
| 0 | — | — | — | 0 |
| 1 | , skip | , skip | 1 | |
| 2 | , skip | , skip | 2 | |
| 3 | , skip | 1 | ||
| 4 | 1 | |||
| 5 | 2 | |||
| 6 | 2 ✓ |
Answer: — two coins of denomination 3.
Interactive Trace
Use the widget below to step through the algorithm visually. You can change the coin denominations and the target amount to see how the DP table adapts in real-time:
Complexity Analysis
| Value | Explanation | |
|---|---|---|
| Time | Two nested loops: amounts (0 to S) × coins (n denominations) | |
| Space | Single dp[] array of size |
For comparison:
| Approach | Time Complexity | |
|---|---|---|
| Brute Force | ops | |
| Dynamic Programming | ops |
Edge Cases
| Scenario | Input | Expected Output |
|---|---|---|
| Target is zero | , | |
| No solution exists | , | |
| Single coin equals target | , | |
| All coins larger than target | , | |
| Only coin of value 1 | , |
Implementation
- Java
- Python
- JavaScript
def coin_change(coins: list[int], amount: int) -> int:
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if coin <= i:
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
function coinChange(coins, amount) {
const dp = new Array(amount + 1).fill(Infinity);
dp[0] = 0;
for (let i = 1; i <= amount; i++) {
for (const coin of coins) {
if (coin <= i && dp[i - coin] + 1 < dp[i]) {
dp[i] = dp[i - coin] + 1;
}
}
}
return dp[amount] === Infinity ? -1 : dp[amount];
}
Variants
Variant A — Count the Number of Ways
Instead of minimizing, count how many distinct combinations sum to (order does not matter).
Change the recurrence and base case:
This corresponds to LeetCode 518 — Coin Change II.
Variant B — Top-Down with Memoization
The same minimum-coins problem solved via recursion + a cache. Mathematically equivalent to the bottom-up approach; some students find it easier to derive.
memo = {}
function minCoins(coins, amount):
if amount == 0: return 0
if amount < 0: return ∞
if amount in memo: return memo[amount]
best = ∞
for each coin c in coins:
best = min(best, minCoins(coins, amount - c) + 1)
memo[amount] = best
return best
Variant C — Bounded Supply (0/1 Knapsack)
Each denomination may be used at most times. This requires a modified recurrence and is an instance of the bounded knapsack problem.
Common Mistakes
-
Initializing
dp[0] = ∞instead of0. The base casedp[0] = 0is the seed from which every solution is built. Getting it wrong corrupts the entire table. -
Array size
Sinstead ofS+1. You need indices0throughSinclusive, so the array must have sizeS + 1. -
Forgetting to check
c ≤ i. Accessingdp[i - c]whenc > imeans accessing a negative index — undefined behavior or a runtime error. -
Returning
dp[S]without checking for∞. If is unreachable,dp[S]stays at its initial∞. Always check and return-1in that case. -
Confusing "minimum coins" with "number of ways." Both are DP problems on the same structure but with different recurrences and different base cases. The state
dp[i]means something entirely different in each variant.
Practice Problems
Solidify your understanding with these related problems:
| Problem | Difficulty | Key Twist |
|---|---|---|
| 322. Coin Change | Medium | Exact problem from this page |
| 518. Coin Change II | Medium | Count combinations instead of minimizing |
| 279. Perfect Squares | Medium | Coin denominations are perfect squares |
| 983. Minimum Cost For Tickets | Medium | Non-uniform cost steps |
| 139. Word Break | Medium | Same DP pattern applied to strings |
Key Takeaways
- Coin Change is a canonical unbounded knapsack problem — the go-to example for learning DP.
- Greedy fails for arbitrary coin sets. Never assume greedy works without a proof.
- The two pillars of DP applicability: optimal substructure and overlapping subproblems.
- The entire solution is two lines of logic: the base case and the recurrence .
- DP reduces the time complexity from exponential to polynomial .
- Always handle the "no solution" case: check if before returning.