Coin Change Problems
Coin change is a classic unbounded knapsack pattern: each coin can be used unlimited times. This lesson covers minimum coins (LeetCode 322), number of combinations (518), greedy vs DP, and full solutions in C, Java, and Python with output trace tables.
Overview
Given coin denominations and a target amount, common questions are:
- Minimum coins — fewest coins to make exact amount (or −1 if impossible)
- Count combinations — how many distinct multisets of coins sum to amount
- Count permutations — order matters (different loop order in DP)
| Problem | Goal | State | LeetCode |
|---|---|---|---|
| Minimum coins | Min count | dp[a] = min coins for amount a | 322 |
| Combinations | Count ways (order ignored) | dp[a] += ways using coins ≤ last | 518 |
| Permutations | Count orderings | Outer loop on amount | 377 |
| Exact sum (0/1) | Each coin once | 2D knapsack | Subset sum variants |
Unbounded coin DP pattern:
dp[0] = base case (0 coins or 1 way)
for each amount a from 1 .. target:
for each coin c:
use dp[a - c] to update dp[a]
Greedy vs Dynamic Programming
Greedy (always pick largest coin) works for canonical systems like US coins {1, 5, 10, 25} but fails for arbitrary denominations.
| Input | Greedy result | Optimal (DP) | Explanation |
|---|---|---|---|
| coins=[1,5,10,25], amount=30 | 6 coins (25+5) | 2 coins (25+5) | Greedy works ✓ |
| coins=[1,3,4], amount=6 | 3 coins (4+1+1) | 2 coins (3+3) | Greedy fails ✗ — need DP |
| coins=[2], amount=3 | Impossible | −1 | Odd amount with even coins |
Coin Change I — Minimum Coins
Problem: Given coins and amount, return the fewest coins needed, or −1 if impossible.
Recurrence: dp[a] = min(dp[a], dp[a − c] + 1) for each coin c ≤ a
Base: dp[0] = 0; initialize other cells to ∞ (or amount + 1)
Minimum Coins Code (C, Java, Python)
Python
def coin_change(coins, amount):
"""LeetCode 322 — minimum coins for exact amount. O(amount × len(coins))."""
INF = amount + 1
dp = [INF] * (amount + 1)
dp[0] = 0 # zero coins needed for amount 0
for a in range(1, amount + 1):
for c in coins:
if c <= a:
# try using coin c: one coin + best for remainder
dp[a] = min(dp[a], dp[a - c] + 1)
return dp[amount] if dp[amount] != INF else -1
if __name__ == "__main__":
tests = [([1, 2, 5], 11), ([2], 3), ([1, 3, 4], 6)]
for coins, amount in tests:
print(f"coins={coins}, amount={amount} → {coin_change(coins, amount)}")
Java
public class CoinChangeMin {
/** Minimum coins — bottom-up unbounded knapsack style. */
public static int coinChange(int[] coins, int amount) {
int INF = amount + 1;
int[] dp = new int[amount + 1];
for (int i = 1; i <= amount; i++) dp[i] = INF;
dp[0] = 0;
for (int a = 1; a <= amount; a++) {
for (int c : coins) {
if (c <= a) {
dp[a] = Math.min(dp[a], dp[a - c] + 1);
}
}
}
return dp[amount] == INF ? -1 : dp[amount];
}
public static void main(String[] args) {
System.out.println(coinChange(new int[]{1, 2, 5}, 11)); // 3
System.out.println(coinChange(new int[]{2}, 3)); // -1
System.out.println(coinChange(new int[]{1, 3, 4}, 6)); // 2
}
}
C
#include <stdio.h>
#define MAX_AMT 1000
/* Minimum coins for amount; returns -1 if impossible */
int coin_change_min(int coins[], int n, int amount) {
int dp[MAX_AMT + 1];
int INF = amount + 1;
int a, i, c;
for (a = 0; a <= amount; a++) dp[a] = INF;
dp[0] = 0;
for (a = 1; a <= amount; a++) {
for (i = 0; i < n; i++) {
c = coins[i];
if (c <= a && dp[a - c] + 1 < dp[a]) {
dp[a] = dp[a - c] + 1;
}
}
}
return dp[amount] == INF ? -1 : dp[amount];
}
int main(void) {
int c1[] = {1, 2, 5}, c2[] = {2}, c3[] = {1, 3, 4};
printf("%d\n", coin_change_min(c1, 3, 11)); /* 3 */
printf("%d\n", coin_change_min(c2, 1, 3)); /* -1 */
printf("%d\n", coin_change_min(c3, 3, 6)); /* 2 */
return 0;
}
Minimum Coins Output & Trace
Program output (all three languages)
| Language | Input | Console Output | Explanation |
|---|---|---|---|
| Python | [1,2,5], 11 | → 3 | 5+5+1 = three coins |
| Python | [2], 3 | → -1 | Cannot make odd 3 with only 2s |
| Python | [1,3,4], 6 | → 2 | 3+3 beats greedy 4+1+1 |
| Java / C | same tests | 3 -1 2 | Identical logic; C prints one per line |
DP trace — coins = [1, 2, 5], amount = 11
| Amount a | dp[a] (min coins) | Best choice | Explanation |
|---|---|---|---|
| 0 | 0 | — | Base case |
| 1 | 1 | 1 | One coin of 1 |
| 2 | 1 | 2 | One coin of 2 |
| 3 | 2 | 2+1 | dp[2]+1 |
| 4 | 2 | 2+2 | Two coins of 2 |
| 5 | 1 | 5 | Single coin 5 |
| 6 | 2 | 5+1 | dp[5]+1 |
| 10 | 2 | 5+5 | Two coins of 5 |
| 11 | 3 | 5+5+1 | dp[10]+1 → answer 3 |
Coin Change II — Combinations
Problem: Count how many combinations of coins sum to amount (order does not matter).
Key: Loop over coins outer, amount inner to avoid counting permutations twice.
Recurrence: dp[a] += dp[a − c] for each coin c
Combinations Code (C, Java, Python)
Python
def coin_change_combinations(coins, amount):
"""LeetCode 518 — count combinations (order ignored)."""
dp = [0] * (amount + 1)
dp[0] = 1 # one way to make amount 0: use no coins
for c in coins: # outer: each coin type once
for a in range(c, amount + 1):
dp[a] += dp[a - c] # add ways that use coin c
return dp[amount]
if __name__ == "__main__":
print(coin_change_combinations([1, 2, 5], 5)) # 4
print(coin_change_combinations([1, 2, 5], 11)) # 11
Java
public class CoinChangeCombinations {
public static int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int c : coins) {
for (int a = c; a <= amount; a++) {
dp[a] += dp[a - c];
}
}
return dp[amount];
}
public static void main(String[] args) {
int[] coins = {1, 2, 5};
System.out.println(change(5, coins)); // 4
System.out.println(change(11, coins)); // 11
}
}
C
#include <stdio.h>
#define MAX_AMT 1000
int coin_change_ways(int coins[], int n, int amount) {
int dp[MAX_AMT + 1] = {0};
int i, a, c;
dp[0] = 1;
for (i = 0; i < n; i++) {
c = coins[i];
for (a = c; a <= amount; a++) {
dp[a] += dp[a - c];
}
}
return dp[amount];
}
int main(void) {
int coins[] = {1, 2, 5};
printf("%d\n", coin_change_ways(coins, 3, 5)); /* 4 */
printf("%d\n", coin_change_ways(coins, 3, 11)); /* 11 */
return 0;
}
Combinations Output & Trace
Program output
| Language | Input | Output | Valid combinations (amount=5) |
|---|---|---|---|
| Python | [1,2,5], 5 | 4 | 5 | 2+2+1 | 2+1+1+1 | 1×5 |
| Python | [1,2,5], 11 | 11 | Eleven distinct multisets |
| Java / C | [1,2,5], 5 | 4 | Same as Python |
| Java / C | [1,2,5], 11 | 11 | Full DP table fill |
DP trace — coins = [1, 2, 5], amount = 5 (combinations)
| After processing | dp[0..5] | Explanation |
|---|---|---|
| Init | [1,0,0,0,0,0] | One way to make 0 |
| Coin 1 | [1,1,1,1,1,1] | +1 way per amount using 1s |
| Coin 2 | [1,1,2,2,3,3] | dp[2]+=dp[0], dp[3]+=dp[1], … |
| Coin 5 | [1,1,2,2,3,4] | dp[5]+=dp[0] → 4 ways total |
Unbounded Knapsack Connection
Coin change is the unbounded subset-sum variant: unlimited copies of each item (coin).
| Variant | Reuse items? | Inner loop direction | Example |
|---|---|---|---|
| 0/1 Knapsack | Each item once | Amount descending | Subset sum with unique coins |
| Unbounded | Unlimited copies | Amount ascending | Coin change, rod cutting |
| Bounded | Limited copies | Binary split / 2D DP | Each coin has a count |
More Examples
Example 1: Impossible amount
| coins | amount | Min coins | Combinations | Why |
|---|---|---|---|---|
| [2, 4] | 7 | −1 | 0 | All sums are even |
Example 2: Greedy failure (again)
| coins | amount | Greedy | DP min | Best combo |
|---|---|---|---|---|
| [1, 3, 4] | 6 | 3 (4+1+1) | 2 | 3+3 |
Example 3: Complexity summary
| Problem | Time | Space | Notes |
|---|---|---|---|
| Min coins | O(amount × coins) | O(amount) | Pseudo-polynomial |
| Combinations | O(amount × coins) | O(amount) | Watch loop order |
| Memoization (min) | O(amount × coins) | O(amount) + stack | Top-down alternative |
Approach Comparison
| Approach | Time | Space | When to use |
|---|---|---|---|
| Greedy | O(n log n) | O(1) | Canonical coin systems only |
| Bottom-up DP | O(A × C) | O(A) | Standard interview solution |
| Top-down memo | O(A × C) | O(A) + stack | Quick recursive draft |
| BFS (min coins) | O(A × C) | O(A) | Alternative; less common |
A = amount, C = number of coin types, n = number of coins sorted for greedy.
Real-Life Applications
Coin change models any situation where you must reach an exact total using a fixed set of denominations—minimizing pieces or counting distinct ways.
| Domain | Real scenario | Coin change mapping | Variant |
|---|---|---|---|
| Retail / POS | Cash register making change for a customer | Coins/bills = denominations; amount = change due; goal = fewest coins | Minimum coins |
| Vending machines | Accepting payment and returning exact change | Available coin tubes = limited supply; amount = refund | Min coins + inventory |
| Banking / ATMs | Dispensing cash in standard note/coin mixes | Denominations = {100, 50, 20…}; amount = withdrawal | Min coins (often greedy works for USD/EUR) |
| Currency exchange | Breaking foreign notes into local coins at a kiosk | Non-standard denominations → DP required (greedy fails) | Minimum coins |
| Accounting | Paying an invoice using exact bill/cheque combinations | Denominations = payment options; amount = invoice total | Count combinations |
| Games / apps | Crafting items from resource bundles (e.g. 3 wood + 2 stone) | Resources = “coins”; target = recipe cost; count valid recipes | Combinations |
| Music / rhythm | Filling a bar with note lengths that sum to exact beats | Note values = denominations; bar length = amount | Count combinations |
| Public transit | Adding fare credit using top-up increments | Top-up amounts = coins; target = exact fare balance | Minimum steps |
Interview Tips
- Clarify: minimum coins, count combinations, or count permutations?
- State
dp[a]meaning before writing loops. - Mention pseudo-polynomial time O(amount × coins).
- For combinations vs permutations, explain outer/inner loop order.
- Give greedy counterexample
[1,3,4], amount=6. - Return −1 when min coins impossible; 0 combinations when amount unreachable.
- Connect to knapsack problems (unbounded variant).
Key Takeaway
Coin change is unbounded knapsack on amount: fill dp[0…amount], each coin updates reachable sums. Minimum coins uses min; counting uses += with careful loop order for combinations vs permutations.
Next up: Knapsack problems · Fibonacci & climbing stairs · Memoization vs tabulation