LeetCode #322 Medium

Coin Change (Minimum Coins)

Given coin denominations and an amount, return the fewest coins to make it, or −1.

dpbfsarray
Open on LeetCode ↗
02

Intuition

Greedy (largest coin first) fails for sets like [1,3,4] making 6. But 'fewest coins for amount a' only depends on fewest coins for smaller amounts: dp[a] = 1 + min(dp[a − coin]). Build up from 0.

How to spot this pattern

Unbounded knapsack: coins may be reused, so the state is just the amount and every coin is retried at every amount. The tell that it's DP rather than greedy is that taking the largest coin first can fail — with coins [1, 3, 4] and amount 6, greedy picks 4+1+1 while the answer is 3+3. Whenever a locally best choice can be beaten later, enumerate.

03

Approach

1

Where greedy breaks

[1,3,4], amount 6: greedy 4+1+1 = 3 coins, optimal 3+3 = 2. Local best isn't global best → DP.

2

Define the subproblem

dp[a] = minimum coins for amount a; dp[0] = 0, everything else ∞ until proven.

3

Fill bottom-up

For each a from 1..amount try every coin: dp[a] = min(dp[a], dp[a−c] + 1). Unreachable amounts stay ∞ → return −1.

04

Solution & live demo

1class Solution:
2 def coinChange(self, coins, amount):
3 INF = float("inf")
4 dp = [0] + [INF] * amount
5 for a in range(1, amount + 1):
6 for c in coins:
7 if c <= a and dp[a - c] + 1 < dp[a]:
8 dp[a] = dp[a - c] + 1
9 return dp[amount] if dp[amount] != INF else -1
05

Common pitfalls

Taking the largest coin greedily

✗ Wrong
for c in sorted(coins, reverse=True):
    while amount >= c:
        amount -= c; count += 1
✓ Right
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

Greedy is only valid for canonical coin systems. On coins = [1, 3, 4], amount = 6 it returns 3 (4+1+1) when 2 (3+3) is optimal. The DP tries every coin at every amount, so no better combination can be missed.

Initialising the table to zero

✗ Wrong
dp = [0] * (amount + 1)
✓ Right
dp = [0] + [INF] * amount

Zero means "reachable with no coins", which is true only for amount 0. Elsewhere it makes unreachable amounts look free and the minimum never rises above 0. Everything except the base case starts unreachable.

Returning dp[amount] without the reachability check

✗ Wrong
return dp[amount]
✓ Right
return dp[amount] if dp[amount] != INF else -1

Amounts that cannot be formed at all — coins = [2], amount = 3 — keep their infinite sentinel, and the problem asks for -1 there. Returning the sentinel leaks an internal marker as if it were an answer.

06

Edge cases

amount = 0

dp[0]=0 — zero coins.

Unreachable amount, e.g. coins=[2], amount=3

dp stays ∞ → −1.

07

Complexity

Time
O(amount × coins)
Space
O(amount)
Classic unbounded-knapsack shape.