Dissecting the DP Coin Change Problem
Hey fellow coders,
Let’s talk about one of those classic dynamic programming problems that haunts us all at some point: Coin Change.
You know the one:
“Given an array of coins and a target amount, return the minimum number of coins that make up that amount.”
Sounds simple enough, right? But if you’ve ever stared at noCoins[i] = min(noCoins[i], noCoins[i - coin] + 1) and wondered what is even going on here, you are not alone.
In this post, I’ll walk you through how I went from confused to clicking when it comes to:
- What the DP array means
- Why we fill it with amount + 1
- Why values like 13 show up
- How this is just a graph in disguise
- And how to actually recover all combinations, not just the count
Let’s get into it!
The Setup
You’re given:
coins = [2, 5, 10]amount = 12
You want to know:What’s the minimum number of coins you can use to sum up to 12?
Dynamic programming is the way to go, and that means we’ll use an array like this:
int[] noCoins = new int[amount + 1];
Where:
- noCoins[i] = the minimum number of coins to make amount i
Step 1: Initialize with “Infinity”
We fill the array like this:
Arrays.fill(noCoins, amount + 1);noCoins[0] = 0;
Why amount + 1 (a.k.a. 13 in our example)?Because:
- It’s guaranteed to be more than the worst-case number of coins needed
- It acts like a stand-in for “not possible” or “infinity”
- We’ll overwrite it later with real values if a solution exists
So initially, for amount = 12, our table looks like:
noCoins = [0, 13, 13, 13, ..., 13]
Step 2: Build Up Solutions from Small to Big
Here’s the key insight:You don’t solve 12 directly.You build up from 1 to 12, asking at each step:
“What’s the best way to make this amount using the coins I have?”
You loop through every amount from 1 to 12, and for each one, you try every coin:
for (int curAmount = 1; curAmount <= amount; curAmount++) { for (int coin : coins) { if (coin <= curAmount) { noCoins[curAmount] = Math.min(noCoins[curAmount], noCoins[curAmount - coin] + 1); } }}
If you're at curAmount = 7 and trying coin 2, you say:
- “If I already know how to make 7 - 2 = 5, and that takes 1 coin, then I can make 7 by adding 1 more coin.”
- So noCoins[7] = min(noCoins[7], noCoins[5] + 1)
This simple idea builds the entire table from the ground up.
Example: coins = [2, 5, 10], amount = 12
You’ll end up with:
makefileCopyEditAmount:0 1 2 3 4 5 6 7 8 9 10 11 12noCoins: 0 13 1 13 2 1 3 2 4 3 1 4 2
Let’s decode this:
- noCoins[2] = 1 → use one 2
- noCoins[5] = 1 → use one 5
- noCoins[12] = 2 → you can make $12 using just two coins: maybe [2, 10] or [5, 5, 2]
But what about those scary 13s?
- noCoins[1] = 13 → you can’t make $1 using only coins {2, 5, 10}
- noCoins[3] = 13 → same deal for $3
The 13s just mean “unreachable with current coins.”
Why This Is Just a Graph
You can think of each amount as a node in a graph, and each coin is an edge that jumps you forward:
0 --(+2)--> 2 --(+2)--> 4 ... \--(+5)--> 5 --(+2)--> 7 ... \--(+10)-> 10 ...
You’re essentially exploring all possible paths to get to node 12.
- The DP approach stores the shortest path (fewest coins) to each node
- Just like BFS in a graph, we go layer by layer
Want to Know What Coins Were Used?
Just track which coin was last used to reach each amount:
int[] lastUsedCoin = new int[amount + 1];if (noCoins[curAmount - coin] + 1 < noCoins[curAmount]) { noCoins[curAmount] = noCoins[curAmount - coin] + 1; lastUsedCoin[curAmount] = coin;}
Now you can retrace your steps from amount back to 0:
int curr = amount;List<Integer> combo = new ArrayList<>();while (curr > 0) { combo.add(lastUsedCoin[curr]); curr -= lastUsedCoin[curr];}
Boom, you’ve recovered one possible coin combination.
But What If You Want All Possible Combinations?
This is where things get fun. You can build a list of all ways to make each amount by doing this:
List<List<List<Integer>>> combinations = new ArrayList<>();for (int i = 0; i <= amount; i++) combinations.add(new ArrayList<>());combinations.get(0).add(new ArrayList<>()); // base: one way to make 0for (int curAmount = 1; curAmount <= amount; curAmount++) { for (int coin : coins) { if (coin <= curAmount) { for (List<Integer> prev : combinations.get(curAmount - coin)) { List<Integer> newCombo = new ArrayList<>(prev); newCombo.add(coin); combinations.get(curAmount).add(newCombo); } } }}
Now combinations.get(12) holds every way you can make 12 with your coin set!
Wrapping It All Up
ConceptInsightnoCoins[i]Stores min # of coins to make iInitialize with amount+1Acts like “infinity” so any real count will be smaller13s in the tableMeans that amount can’t be made with given coinsEach amount = graph nodeCoins are edges that move you forward in the graphDP = shortest path finderFinds the fewest coins (like BFS)Track lastUsedCoin[]Allows reconstruction of one valid combinationUse List<List<List<Integer>>>Stores all combinations for each amount
Understanding the coin change problem is like learning a new lens to see DP, graphs, and even recursion in disguise. We’re just walking a graph of totals, building paths one coin at a time.
Related Posts
The Tech Landscape in 2026: Key Trends and Unexpected Turns
In the whirlwind of technological advancement, 2026 has already proven to be a year of remarkable shifts and revelations. From regulatory changes in advertising to the resurgence of physical buttons.
The Tech Landscape in 2026: Key Trends, Insights, and Surprises
As we dive into 2026, the tech world is buzzing with developments that are reshaping industries and consumer behavior alike. From groundbreaking advancements in artificial intelligence to the latest i...
The Evolving Landscape of Cybersecurity: Insights and Implications
In our increasingly digital world, cybersecurity has become a hot topic—not just for techies and IT departments, but for everyone who uses the internet. As cyber threats grow more sophisticated and wi...