Coin Change Problem (Dynamic Programming)
Coin Change is one of the most well-known dynamic programming problems. Given a target amount and a set of coin denominations, we need to determine:
- the minimum number of coins needed to make the amount, or
- the number of different ways to make the amount using the coins.
In this lesson, we will focus on the problem of the minimum number of coins.
Problem Definition
We are given an array of coins coins[] and a target amount S.
Each coin can be used an unlimited number of times.
We need to find the minimum number of coins needed to make the amount S.
Example:
- Coins: {1, 3, 4}
- Amount: 6
Answer: 2 coins (3 + 3; 4 + 1 + 1 requires 3 coins).
Why Dynamic Programming?
The problem has:
- overlapping subproblems – the same amount is computed multiple times
- optimal substructure – the optimal solution for amount S depends on optimal solutions for smaller amounts
This makes it an ideal candidate for dynamic programming.
DP Idea and State
We define an array:
dp[x] = the minimum number of coins needed to make amount x
Base case:
dp[0] = 0(no coins are needed to make amount 0)- all other values are initially set to “infinity”
Transition:
dp[x] = min(dp[x], dp[x - coin] + 1)
for each coin coin and each amount x ≥ coin.
Bottom-Up Implementation (C++)
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;
int main() {
// n - number of coins
// S - target amount
int n, S;
cin >> n >> S;
// Read the coin values
vector<int> coins(n);
for (int i = 0; i < n; i++) {
cin >> coins[i];
}
// dp[x] = the minimum number of coins needed to make amount x
// Initialize all values to INT_MAX (representing "infinity")
vector<int> dp(S + 1, INT_MAX);
// Base case: no coins are needed to make amount 0
dp[0] = 0;
// Calculate dp values for all amounts from 1 to S
for (int x = 1; x <= S; x++) {
// Try every coin
for (int coin : coins) {
// Check whether the coin can be used
// and the previous state is not "infinity"
if (x - coin >= 0 && dp[x - coin] != INT_MAX) {
// Choose the smaller value:
// - the current dp[x]
// - or dp[x - coin] + 1 (using one additional coin)
dp[x] = min(dp[x], dp[x - coin] + 1);
}
}
}
// If dp[S] is still INT_MAX, the target amount cannot be formed
if (dp[S] == INT_MAX) {
cout << "Cannot form the target amount";
} else {
cout << "Minimum number of coins: " << dp[S];
}
return 0;
}
Time and Space Complexity
- Time complexity: O(n · S)
- Space complexity: O(S)
where n is the number of coin denominations, and S is the target amount.
Coin Change Visualization (Minimum Number of Coins)
This visualization shows the DP array being filled step by step to find the minimum number of coins.
Coins: {1, 3, 4}, Amount: 6.
Coin Change Variations
Coin Change has several common variations:
- number of ways to make an amount
- limited number of coins
- whether different coin orders count as distinct ways
All these variants can be solved with dynamic programming by making small changes to the state and transition.
Pored osnovnog problema the minimum number of coins, Coin Change ima nekoliko čestih varijacija. Razumevanje ovih varijanti pomaže pri rešavanju složenijih zadataka iz dinamičkog programiranja.
1️⃣ Number of Ways to Make an Amount (Order Does Not Matter)
Goal: determine how many different coin combinations can make the amount S.
Each coin can be used without limit, but the order of the coins does not matter.
DP state:dp[x] represents the number of ways to make amount x.
// Initialization
dp[0] = 1; // postoji 1 način da se formira suma 0: ništa ne uzimamo
for (int coin : coins) {
for (int x = coin; x <= S; x++) {
dp[x] += dp[x - coin]; // add all ways to form x - coin
}
}
Example: coins {1, 3, 4}, amount 6 → number of combinations = 4: - {1,1,1,1,1,1} - {1,1,4} - {3,3} - {1,1,1,3}
2️⃣ Number of Ways to Make an Amount (Order Matters)
If coin order matters, the order of the DP loops changes:
// Initialization
dp[0] = 1;
for (int x = 1; x <= S; x++) {
for (int coin : coins) {
if (x - coin >= 0) {
dp[x] += dp[x - coin]; // add all ways from the previous amount
}
}
}
Example: coins {1, 3, 4}, amount 6 → different sequences are counted separately when order matters.
Coin Change Visualization — Number of Ways to Make an Amount
This visualization shows the DP array being filled step by step to count the different coin combinations.
Coins: {1, 3, 4}, Amount: 6.
Coin order does not matter.
Coin Change — Counting Ways (Visual Walkthrough)
Example:
- Coins: {1, 2, 3}
- Target amount: 4
Initializing the DP Array
We define dp[x] = number of ways to make an amount x.
dp[0] = 1 // there is 1 way to form the sum 0: we take nothing
dp[1..4] = 0
Home table:
| x | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| dp[x] | 1 | 0 | 0 | 0 | 0 |
Step 1: coin = 1
We add all combinations that end with 1:
| x | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| dp[x] | 1 | 1 | 1 | 1 | 1 |
Explanation:
- dp[1] += dp[0] → 1
- dp[2] += dp[1] → 1
- dp[3] += dp[2] → 1
- dp[4] += dp[3] → 1
Step 2: coin = 2
We add all combinations that end with 2:
| x | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| dp[x] | 1 | 1 | 2 | 2 | 3 |
Explanation:
- dp[2] += dp[0] → 1 + 1 = 2 (combinations: 1+1, 2)
- dp[3] += dp[1] → 1 + 1 = 2 (combinations: 1+1+1, 1+2)
- dp[4] += dp[2] → 1 + 2 = 3 (combinations: 1+1+1+1, 1+1+2, 2+2)
Detaljno objašnjenje reda: dp[4] += dp[2]
At this point, we have:
dp[4] = 1→ combinations: 1+1+1+1dp[2] = 2→ combinations: 1+1 i 2
Now we ask:
How many ways are there to make 4 if the last coin is 2?
If the last coin must be 2, then the amount we must make beforehand is:
4 - 2 = 2
The number of ways to make 2 je dp[2] = 2.
We can extend each of those combinations by adding one more coin 2:
- (1 + 1) + 2 → 1 + 1 + 2
- (2) + 2 → 2 + 2
Therefore, we get 2 new combinations.
Since we already had 1 kombinaciju (1+1+1+1), the total becomes:
1 (stara) + 2 (nove) = 3
That is why we do this:
dp[4] += dp[2]
Intuitively:
“Take every way to make 2 and add one more 2 to each.”
Vizuelni dijagram grananja combination
Let us see how the combinations for amount 4 are formed when processing coin 2.
We know that:
- dp[2] = 2 → (1+1) i (2)
Now we add one more coin to each of those combinations 2:
(pravimo 4 dodavanjem 2)
4
|
------------------------
| |
(1+1) (2)
| |
(1+1)+2 (2)+2
| |
1+1+2 2+2
These are two new combinations that end with 2.
Together with the existing combination:
1 + 1 + 1 + 1
At this point, we have a total of 3 combinations for amount 4.
Ključna intuicija
We can think of it this way:
- dp[x] = the total number of combinations for x
- dp[x - coin] = the number of combinations we can extend by adding that coin
Each combination for x - coin becomes a new combination for x
when we add one more coin.
That is why we add:
dp[x] += dp[x - coin]
This is a systematic way to build combinations layer by layer.
Step 3: coin = 3
We add all combinations that end with 3:
| x | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| dp[x] | 1 | 1 | 2 | 3 | 4 |
Explanation:
- dp[3] += dp[0] → 2 + 1 = 3 (kombinacije: 1+1+1, 1+2, 3)
- dp[4] += dp[1] → 3 + 1 = 4 (kombinacije: 1+1+1+1, 1+1+2, 2+2, 1+3)
Final Result
The number of different combinations for amount 4 je: dp[4] = 4
This approach guarantees that the order of the coins does not change the combination, because we process each coin from the outside and build combinations systematically.
3️⃣ Limited Number of Coins
If there is a limit on how many times each coin can be used, it is necessary to keep track of how many coins have been used. This is usually done with 2D DP:
// dp[i][x] = the minimum number of coins for amount x koristeći prvih i kovanica
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int x = 0; x <= S; x++) {
dp[i][x] = dp[i-1][x]; // We don't use that coin.
for (int k = 1; k <= limit[i] && k*coins[i-1] <= x; k++) {
if (dp[i-1][x - k*coins[i-1]] != INT_MAX)
dp[i][x] = min(dp[i][x], dp[i-1][x - k*coins[i-1]] + k);
}
}
}
This is similar to 0/1 Knapsack, but allows multiple copies of each coin denomination.
All these variants can be solved with dynamic programming by making small changes to the state and transition. Understanding these variations makes it easier to solve problems that combine:
- minimum number of coins
- number combination
- restrictions on the use of coins
Visualization — Number of Ways (Order Matters)
Coins: {1,3,4}
Amount: 6
Now different orders count as different ways.
Visualization — Limited Number of Coins
Coins: {1,3,4} Limit: {2,1,1} Amount: 6
Greedy Algorithms — A Counterexample
Greedy algorithms choose the current best solution at each step, without thinking about future consequences.
Sometimes it gives an optimal solution (eg activities with the earliest completion), but it often leads to the wrong result.
An Example Where Greedy Does NOT Work
Consider the Coin Change problem:
- Coins: {1, 3, 4}
- Amount: 6
A greedy strategy would be: "Always set the biggest coin possible."
Steps:
- Take 4 → remaining 2
- Take 1 → remaining 1
- Take 1 → remaining 0
The greedy solution uses 3 coins (4 + 1 + 1).
But the optimal solution is:
- 3 + 3 = 6
which uses only 2 kovanice.
Conclusion
The Greedy algorithm made the locally best decisions, but did not find a global optimal solution.
Therefore:
- If a problem has optimal substructure and overlapping subproblems, dynamic programming may be suitable.
- If you can prove that a locally optimal choice always leads to the global optimum, the greedy approach is valid.
Before applying greedy, always try to find a counter example. If you find it - greedy is not a safe choice.
Greedy vs. Dynamic Programming (Visual Comparison)
Consider the same example:
- Coins: {1, 3, 4}
- Amount: 6
Greedy Approach
Strategy: “Always take the largest possible coin.”
| Step | Coin Chosen | Remaining Amount |
|---|---|---|
| 1 | 4 | 2 |
| 2 | 1 | 1 |
| 3 | 1 | 0 |
Greedy result: 3 kovanice
⚠ Problem: the algorithm never considered the combination 3 + 3.
Dynamic Programming (DP)
DP computes the optimal solution for every amount from 0 do 6.
Definition:
dp[x] = the minimum number of coins for amount x
Resulting table:
| Suma x | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| dp[x] | 0 | 1 | 2 | 1 | 1 | 2 | 2 |
DP result: 2 coins
Key Difference
| Greedy | Dynamic programming |
|---|---|
| Makes a local decision | Considers all sub-solutions |
| Does not check alternatives | Keeps optimal results for smaller sums |
| Quick and easy | Surely gives the optimal solution |
| Could be wrong | Doesn't miss the optimal combination |
Intuition
Greedy asks: “What is best right now?”
DP thinks: "What was best for all the minor problems, so I'll use that to get the best solution now.”
That is why DP can often be seen as a "safe version" of the greedy approach, which checks all possibilities.
Connections to Other DP Problems
The Coin Change problem is closely related to several other classic dynamic programming problems. Understanding these connections helps you to:
- recognize DP patterns more easily
- apply similar techniques to different problems
- solve contest problems faster
1. 0/1 Knapsack Problem
This is one of the most famous DP problems. The goal is to maximize the total value of the items that fit in the
backpack
of W capacity, where each item can be taken at most once.
The Coin Change minimum-coin problem is essentially **a special case of “Unbounded Knapsack”** — gde:
- item weights correspond to coin denominations
- each “item” can be used an unlimited number of times
- the goal is to minimize the number of items (coins) used
On the other hand, the 0/1 Knapsack has an important difference: each item can be taken at most once, resulting in a different DP formula and 2D DP table. You can see a detailed explanation and examples here:
2. Subset Sum Problem
Subset Sum is another simple but very important DP task:
A set of numbers and a target amount T are given. Check if there is a subset whose sum is **true
T**.
The DP approach uses the array dp[x] to indicate whether the sum x is possible. This
is very close
Coin Change variants where we only check the possibility of forming a sum, not the number of ways or minimum
coins.
Read the full lesson here:
3. Knapsack Variants — Unbounded and Bounded
Technically, Coin Change is an **Unbounded Knapsack** problem: svaku kovanicu možemo koristiti više puta. To je zato što prelaz u DP koristi samo jednu dimenziju i dozvoljava višestruko korišćenje iste „težine”.
To learn more about all Knapsack variants — 0/1, *unbounded*, and *bounded* — see:
Contest Preparation — Knapsack and Its Variants
4. Path Counting and Combinatorial DP
The Coin Change variant that counts all ways to form an amount is an example of combinatorial dynamic programming:
- we fill the array
dp[x]with numbers combination - loop order matters: iterate over coins first, then amounts
This principle often appears in other problems as well - eg. when counting roads in the network (Grid DP) or counting combination of subsets. Although we don't have a separate lesson just for that yet, this idea is used in other parts of the DP section of the site, esp in scheduling and combinatorics problems.
5. Other Related DP Topics
Coin Change is a DP problem involving numbers and sums, like many problems that apply DP to arrays and strings:
- DP: Longest Common Substring (LCS) – A classic string DP problem.
- Problems such as Edit Distance, Grid DP, and Counting Paths use similar state-and-transition ideas.
In short: Coin Change fits into the broader family of DP issues. If you master the connections between them, you will be able to:
- recognize the right DP pattern for a new problem
- define the DP state and transition clearly
- use 1D or 2D DP in different situations
After studying Coin Change, it is useful to explore these related topics; they build broader DP intuition and make complex problems easier to solve.
Practice Problems (Coin Change — DP)
Problem 1:
Given the coin denominations {1, 2, 5}. Determine the minimum number of coins needed to forms the sum 11.
Hint (short):
Create a DP array of dimensions dp[0..11] where dp[x] represents the minimum number of
coins
for amount x:
dp[0] = 0
dp[x] = min(dp[x - coin] + 1) za sve coin u {1,2,5} ako x - coin >= 0
Be careful not to use a negative index.
Before viewing the solution, try to:
- Think about which combinations of coins can make 11.
- Estimate the minimum number of coins intuitively.
Problem 2:
Given the coin denominations {1, 2, 5}, determine in how many different ways the amount S = 7 can be formed. Coin order does not matter.
Hint (short):
dp[x] = number of ways to make an amount x
dp[0] = 1
Problem 3:
The coins are given as follows:
- Values: {1, 3, 4}
- Usage limits: {2, 1, 1}
Determine whether it is possible to make the amount S = 6.
Problem 4:
Given coins with denominations {1, 3, 4}, determine the minimum number of coins needed to make the amount S = 6.
Each coin can be used an unlimited number of times.
Hint (short):
dp[x] = the minimum number of coins for amount x
dp[0] = 0
Before clicking the button to show the solution, try to solve the problem on your own:
- Calculate the solution by hand.
- Consider whether the greedy approach always gives an optimal solution.
- Fill in at least the first few values of the
dparray.
Problem 4b: Reconstructing the Solution from the DP Array
After calculating the minimum number of coins using the DP array, we often want to know exactly which coins make up the optimal solution.
The goal of this task is to determine not only the minimum number of coins, but also the specific coin values used in the solution.
Before viewing the solution:
- Look at the DP array for amounts from 0 to S.
- Try to trace the path backwards by hand.
- Consider whether multiple optimal solutions exist.
Problem 4c: Reconstruction Using an Auxiliary Array
In the previous task, reconstruction was performed by directly checking the DP array. Now we will use a special array that remembers which choice led to the optimal solution.
This approach is common in practice and is also used in:
- 0/1 Knapsack problems
- Edit distance algorithms
- Shortest-path algorithms in graphs
Before viewing the solution, try to answer:
- What does
choice[x]represent? - Why is it enough to remember only one coin for each amount?
- Does this approach discard any optimal solutions?
Problem 4d: Filling the DP Array — Visual Walkthrough
In this part, we are not writing new code.
Instead, we visually track
how the dp and choice arrays are filled
for the minimum-coin problem.
Reminder:
- Coins: {1, 3, 4}
- Target amount: S = 6
Step 0 — Initial State
| Amount (x) | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| dp[x] | 0 | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ |
| choice[x] | - | - | - | - | - | - | - |
Step 1 — Amount x = 1
We can use coin 1:
dp[1] = dp[0] + 1 = 1
choice[1] = 1
Step 2 — Amount x = 2
The best choice is to use two coins of value 1:
dp[2] = dp[1] + 1 = 2
choice[2] = 1
Step 3 — Amount x = 3
Coin 3 gives a better solution than three coins of value 1:
dp[3] = dp[0] + 1 = 1
choice[3] = 3
Step 4 — Amount x = 4
The best option is to use coin 4:
dp[4] = dp[0] + 1 = 1
choice[4] = 4
Step 5 — Amount x = 5
Possible options:
- 4 + 1 → 2 coins
- 3 + 1 + 1 → 3 coins
dp[5] = dp[4] + 1 = 2
choice[5] = 1
Step 6 — Amount x = 6
The best solution is:
- 3 + 3 → 2 coins
dp[6] = dp[3] + 1 = 2
choice[6] = 3
Final DP and Choice Arrays
| Amount (x) | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| dp[x] | 0 | 1 | 2 | 1 | 1 | 2 | 2 |
| choice[x] | - | 1 | 1 | 3 | 4 | 1 | 3 |
Reconstruction:
6 → 6 - 3 = 3
3 → 3 - 3 = 0
Solution: 3 + 3
Problem 4e: Animated DP — Highlighting Each Step
Clicking the button shows how the DP array is filled and which amount is currently being processed.
Coins: {1, 3, 4}
Target amount: 6
Initial state: dp[0] = 0. Click "Next step" to begin.
