Hello! Welcome back to our final lesson in the Dynamic Programming module.
In our last session, we mastered the 0/1 and Unbounded Knapsack patterns, focusing on how to make optimal choices to maximize value under a capacity constraint. The key idea for Unbounded Knapsack was that we could use an item multiple times.
Today, we'll see how two of the most common interview problems—the Coin Change variations—are direct applications of this Unbounded Knapsack pattern. Mastering these problems will solidify your understanding of DP and prepare you for a category of questions that frequently appear in technical interviews.
By the end of this lesson, you will be able to:
- Solve the "Minimum Coins" variation to find the fewest coins for a given amount.
- Solve the "Coin Combinations" variation to count the number of ways to make an amount.
1. Coin Change: Minimum Number of Coins
This is one of the most classic dynamic programming problems.
Problem: Given an array of coin denominations and a total amount, find the fewest number of coins required to make up that amount. If it's impossible, return -1. You have an infinite supply of each coin.
This is LeetCode 322 and is a clear example of an Unbounded Knapsack optimization problem:
- Knapsack Capacity (
W): The targetamount. - Items: The
coins. - Item Weights: The coin denominations
coin_value. - Item Values: The value of each coin is simply
1, because we want to minimize the count of coins.
Why a Greedy Approach Fails
A natural first thought is to be greedy: always take the largest coin possible. For coins = [1, 5, 10, 25] and amount = 30, this works: 25 + 5. But what about coins = [1, 3, 4] and amount = 6?
- Greedy: Take
4. Remaining amount is2. Can't use3or4. So we take1twice. Total:4 + 1 + 1(3 coins). - Optimal: Take
3twice. Total:3 + 3(2 coins).
The greedy approach fails. We must explore all combinations, which points us to dynamic programming.
The Bottom-Up DP Solution
We can solve this by building up a solution for every amount from 1 up to our target. Let dp[i] be the minimum number of coins needed to make amount i.
-
State:
dp[i]= minimum coins for amounti. -
Initialization: We create a
dparray of sizeamount + 1. We can initialize all values to a stand-in for infinity (likeamount + 1, since the answer can't be more thanamountif a solution exists). We setdp[0] = 0, as it takes zero coins to make an amount of 0. -
Recurrence Relation: For each amount
afrom 1 toamount, we try everycoin:
Ifa >= coin, we have a potential new way to makea. The number of coins would be1(for the current coin) plus the minimum coins needed for the remaining amount, which isdp[a - coin]. We want the minimum over all possible coin choices.The recurrence for each amount
ais:
The following video provides an excellent walkthrough of this entire thought process.
Coin Change - Dynamic Programming Bottom Up - Leetcode 322
The NeetCode video 'Coin Change - Dynamic Programming Bottom Up' breaks this problem down perfectly. It starts by showing why greedy fails, visualizes the decision tree, and then builds the intuitive bottom-up DP solution.
Watch the following segments: 00:00 - 03:08: Understand the problem and, crucially, the counter-example for a greedy strategy. 10:47 - 15:26: This is the most important part. See how the bottom-up DP table is constructed, from the base case dp[0] up to the target amount. 15:26 - 19:23: Review the Python code implementation and the final complexity analysis.
Complexity Analysis:
- Time Complexity: O(amount * num_coins). We have two nested loops: one iterating through amounts and one through coins.
- Space Complexity: O(amount) for our
dparray.
2. Coin Change: Number of Combinations
Now, let's look at the second major variation: instead of finding the minimum, we need to count the total number of unique combinations.
Problem: Given an array of coin denominations and a total amount, find the number of combinations that make up that amount. You have an infinite supply of each coin.
This is LeetCode 518. It's an Unbounded Knapsack counting problem. The key challenge here is to count combinations, not permutations. For example, [1, 2] and [2, 1] should be counted as one combination.
The Bottom-Up DP Solution (Space-Optimized)
To avoid counting permutations, we build our solution one coin at a time.
Let dp[j] be the number of ways to make amount j using the coins we have considered so far.
We iterate through the coins one by one. For each coin, we update the dp array for all amounts that this coin can contribute to.
-
State:
dp[j]= number of ways to make amountj. -
Initialization: We create a
dparray of sizeamount + 1initialized to all zeros.dp[0]is set to1, because there is exactly one way to make an amount of 0: by choosing no coins. This is our crucial base case. -
Recurrence Relation:
We use a nested loop structure:- Outer Loop: Iterate through each
coinincoins. - Inner Loop: Iterate through each amount
jfromcoinup toamount.
For each
j, we can form it by adding the currentcointo any combination that formedj - coin. So, we updatedp[j]by adding the number of ways we could formj - coin. - Outer Loop: Iterate through each
This structure ensures we only count combinations. By processing one coin fully before moving to the next, we guarantee a fixed order of coins in our combinations (e.g., smaller denominations are always considered before larger ones).

This approach is best understood through a clear explanation.
Coin Change 2 - Dynamic Programming Unbounded Knapsack - Leetcode 518 - Python
The NeetCode video 'Coin Change 2' explains this counting variation. It clearly distinguishes it from the minimum coins problem and walks through the 2D DP logic before presenting the more common 1D optimized solution.
Watch the following segments to understand the solution: 07:51 - 13:32: Focus on the 2D DP (tabulation) approach. This builds intuition for how choices are combined. 13:23 - 17:19: This explains the space-optimized approach using a 1D array. Pay close attention to the order of the loops (coins on the outside, amount on the inside) and why it works to count combinations. 21:28 - 23:09: A quick look at the final, clean Python code for the 1D DP solution.
Test your understanding!
In the Coin Change 2 (combinations) problem, what would happen if you swapped the loops? That is, if you put the amount loop on the outside and the coin loop on the inside:
# What does this code calculate?
dp = [0] * (amount + 1)
dp[0] = 1
for j in range(1, amount + 1):
for coin in coins:
if j >= coin:
dp[j] += dp[j - coin]
Show answer
This code would calculate the number of permutations, not combinations.
For example, with coins = [1, 2] and amount = 3, this code would count both 1+1+2, 1+2+1, 2+1+1 from the coin 1 and 2, and also combinations like 1+2 and 2+1 as distinct. The original loop order (coins outside, amount inside) prevents this by ensuring a fixed order of coins (e.g., 1s are always considered before 2s).
Conclusion
Congratulations on completing the Dynamic Programming module! The Coin Change problems are not only practical applications of the Unbounded Knapsack pattern but also fundamental DP questions that test core problem-solving skills valued in interviews.
Key Takeaways:
- Two Main Variations:
- Minimum Coins (Optimization): Use
min()to find the optimal solution. The recurrence isdp[a] = min(dp[a], 1 + dp[a - coin]). - Number of Ways (Counting): Use addition to count all possible combinations. The recurrence is
dp[j] += dp[j - coin].
- Minimum Coins (Optimization): Use
- Pattern Recognition: Both problems are classic examples of Unbounded Knapsack, where items (coins) can be reused.
- Implementation Details Matter: The loop order in the combination counting problem is a subtle but critical detail that separates a correct solution (combinations) from an incorrect one (permutations).
Preview of the Next Lesson:
We are now moving on to a new major topic: Graph Traversal Algorithms. In the next lesson, we will lay the groundwork by learning how to represent graphs in code, specifically using adjacency lists and adjacency matrices. This will prepare us for implementing fundamental algorithms like Breadth-First Search (BFS) and Depth-First Search (DFS).