Coin Change
Problem
Given an integer array coins representing coin denominations and an integer amount, return the fewest number of coins needed to make up that amount. If the amount cannot be made up by any combination of the coins, return -1.
- 1 ≤ coins.length ≤ 12
- 1 ≤ coins[i] ≤ 2³¹ - 1
- 0 ≤ amount ≤ 10⁴
Example
coins = [1, 2, 5], amount = 113The brute-force approach tries all combinations of coins to sum to 11, which is exponential and inefficient. The optimal solution uses recursion with memoization to avoid redundant calculations. For example, to compute dp(11), the algorithm tries dp(10), dp(9), and dp(6) by subtracting coins 1, 2, and 5 respectively. Each subproblem is solved once and cached. The critical moment is when dp(6) is computed, which itself depends on smaller amounts. By caching results, the algorithm avoids recomputing dp(6) multiple times, drastically reducing the total work. The final answer is dp(11) = 3, corresponding to coins [5,5,1].
Approach
Straightforward Solution
A brute-force recursive approach tries all combinations of coins to sum to the amount, leading to exponential time complexity because it recomputes the same subproblems multiple times.
Core Observation
The minimum number of coins needed for amount A depends on the minimum number needed for smaller amounts A - coin, for each coin denomination. This recursive dependency forms overlapping subproblems with optimal substructure, making Dynamic Programming the natural fit.
Path to Optimal
PreviewRecognizing overlapping subproblems, memoization caches results of dp(rem) calls to avoid redundant work. The problem is reframed as: for each amount rem, find the minimum among dp(rem - coin) + 1 for all coins…
Full step-by-step walkthrough on Pro →
Optimal Approach
PreviewImplement a recursive function dp(rem) that returns the minimum coins needed for rem. Base cases handle rem < 0 (impossible) and rem == 0 (zero coins)…
Full step-by-step walkthrough on Pro →
Want the full reasoning chain?
Unlock the complete walkthrough, line-by-line analysis, and recall drill.
Unlock ProTime
O(amount * n)
Each amount from 0 to amount is computed at most once, and for each amount, the algorithm iterates over all n coin denominations, resulting in O(amount * n) time.
Space
O(amount)
The memoization dictionary stores results for each amount up to the target, requiring O(amount) auxiliary space. The recursion stack depth is at most amount, which is also O(amount).
Pattern Spotlight
Dynamic Programming (Top-Down Memoization)
When a problem involves finding an optimal value over overlapping subproblems with recursive dependencies, use memoization to cache results and avoid exponential recomputation, transforming brute-force recursion into efficient DP.
Solution
| 1 | class Solution:
|
| 2 | def coinChange(self, coins: list[int], amount: int) -> int:
|
| 3 | memo = {}
|
| 4 |
|
| 5 | def dp(rem):
|
| 6 | if rem < 0:
|
| 7 | return float('inf')
|
| 8 | if rem == 0:
|
| 9 | return 0
|
| 10 | if rem in memo:
|
| 11 | return memo[rem]
|
| 12 |
|
| 13 | min_coins = float('inf')
|
| 14 | for coin in coins:
|
| 15 | res = dp(rem - coin)
|
| 16 | if res != float('inf'):
|
| 17 | min_coins = min(min_coins, res + 1)
|
| 18 |
|
| 19 | memo[rem] = min_coins
|
| 20 | return min_coins
|
| 21 |
|
| 22 | result = dp(amount)
|
| 23 | return result if result != float('inf') else -1 |
Step-by-Step Solution
Initialize Memoization Cache and Define Recursive DP Function
| 3 | memo = {}
|
| 5 | def dp(rem):
|
| 6 | if rem < 0:
|
| 7 | return float('inf')
|
| 8 | if rem == 0:
|
| 9 | return 0
|
| 10 | if rem in memo:
|
| 11 | return memo[rem]
|
Objective
To set up a cache for storing computed results and define the recursive function that computes the minimum coins for a given remainder.
Key Insight
Memoization is essential to avoid recomputing the minimum coins for the same remainder multiple times. Defining the recursive dp(rem) function encapsulates the problem of finding the minimum coins for any sub-amount rem, enabling a clean top-down DP approach.
Interview Quick-Check
Core Logic
Memoization cache stores results for each remainder to ensure each subproblem is solved once.
State & Boundaries
The dp function handles base cases for rem < 0 (impossible) and rem == 0 (zero coins).
Common Pitfalls & Bugs
Forgetting to check the memo before recursion leads to exponential time complexity.
Compute Minimum Coins by Exploring All Coin Choices
To recursively compute the minimum coins needed for the current remainder by trying all coin denominations and selecting the best option.
Cache Computed Result and Return Final Answer
To store the computed minimum coins for the current remainder in the memo and return the final result for the original amount.
2 more steps with full analysis available on Pro.
Line Analysis
This solution has 2 Critical lines interviewers watch for.
memo = {}
Initialize a dictionary to memoize results for subproblems.
This cache is critical to store the minimum coins needed for each remainder, preventing redundant recursive calls and reducing exponential complexity to polynomial.
def dp(rem):
Define the recursive function dp that computes minimum coins for remainder rem.
Encapsulating the problem as dp(rem) allows a clean top-down dynamic programming approach that naturally expresses the problem's optimal substructure.
Full line-by-line criticality + rationale for all 17 lines available on Pro.
Test Your Understanding
Why is memoization critical in this recursive solution for coin change?
See the answer with Pro.
Related Problems
Dynamic Programming pattern
Don't just read it. Drill it.
Reconstruct Coin Change from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Coin Change drill