The Coin Change problem is a classic challenge in computer science and dynamic programming. It asks: given a set of coin denominations and a target sum, how many distinct combinations of those coins can be used to form that exact sum? It's assumed you have an unlimited supply of each coin type.
This problem is fundamental for understanding combinatorial mathematics and dynamic programming concepts. It has practical applications in financial systems, vending machines, and any scenario requiring change calculation.
Key Problem Examples
Let's examine a few examples to clarify the problem:
Example 1: Target sum = 4, coins = [1, 2, 3]
- Output: 4
- Explanation: The four solutions are: [1,1,1,1], [1,1,2], [2,2], [1,3].
Example 2: Target sum = 10, coins = [2, 5, 3, 6]
- Output: 5
- Explanation: The five solutions are: [2,2,2,2,2], [2,2,3,3], [2,2,6], [2,3,5], [5,5].
Example 3: Target sum = 10, coins = [10]
- Output: 1
- Explanation: The only solution is to use one coin of value 10.
Example 4: Target sum = 5, coins = [4]
- Output: 0
- Explanation: It's impossible to make the sum 5 with only coins of denomination 4.
A Naive Recursive Approach
The most straightforward way to solve this is with recursion. The core idea is that for any coin, you have two choices:
- Include the coin: Subtract its value from the target sum and recursively process the same set of coins with the new, smaller sum.
- Exclude the coin: Move on to the next coin in the set without changing the target sum.
The total number of ways is the sum of the ways from these two choices.
Recurrence Relation and Base Cases
This logic is formalized by the recurrence relation:count(coins, n, sum) = count(coins, n, sum - coins[n-1]) + count(coins, n-1, sum)
You must define base cases to stop the recursion:
- If the
sumis 0, there is exactly 1 way to make change: by selecting no coins. - If the
sumbecomes negative or there are no coins left to consider (n == 0), there are 0 ways.
Complexity Analysis: This method is simple but highly inefficient. Its time complexity is O(2^sum) in the worst case, as it explores every possible combination. The space complexity is O(sum) due to the recursion call stack.
Enhanced Approach: Top-Down DP with Memoization
The recursive solution recalculates the same subproblems repeatedly. We can optimize it using Dynamic Programming (DP) and memoization.
This approach leverages two key concepts:
- Optimal Substructure: The solution to the main problem can be constructed from optimal solutions to its subproblems.
- Overlapping Subproblems: The same smaller problems are solved multiple times.
Implementation Steps
- Create a 2D memoization array,
dp, with dimensions(n+1) x (sum+1). The valuedp[i][j]will store the number of ways to make the sumjusing the firsticoins. - Initialize the array. The base case
dp[i][0] = 1for anyibecause there's one way to make a sum of 0. - Write a recursive function that checks the
dptable before computing a result. If the value for(i, j)is already computed, return it immediately. - If not, use the recurrence relation to compute the value and store it in the table before returning it.
This method drastically reduces the number of computations. 👉 Explore more strategies for dynamic programming to deepen your understanding.
Complexity Analysis: The time and space complexity are both O(n*sum), as each subproblem is solved only once and stored in a 2D table.
Streamlined Approach: Bottom-Up DP with Tabulation
The top-down method uses recursion, which can lead to stack overflow for large inputs. The bottom-up, or iterative, approach avoids this by building the solution from the ground up.
Building the DP Table
- Initialize a 2D DP table
dp[n+1][sum+1]. - Set
dp[i][0] = 1for alli, as before. - Iterate over each coin (from the first to the last) and each possible sum (from 0 to the target sum).
For each cell
dp[i][j]:If the current coin's value is less than or equal to
j, the number of ways is the sum of:- Ways to make the sum
jwithout this coin:dp[i-1][j] - Ways to make the sum
(j - current_coin_value)using the firsticoins (including the current one):dp[i][j - coins[i-1]]
- Ways to make the sum
- If the coin's value is greater than
j, simply carry over the value from the row above:dp[i][j] = dp[i-1][j]
Complexity Analysis: This approach also has O(n*sum) time and space complexity. It systematically fills the table without the overhead of recursive calls.
Optimal Approach: Space-Optimized Dynamic Programming
If you observe the bottom-up process, you only need information from the current row and the previous row to compute the next state. This means we can optimize the space complexity.
Using a 1D Array for DP
Instead of a 2D table, we can use a 1D array dp[sum+1].
- Initialize
dp[0] = 1(one way to make sum 0). For each coin in the set:
For each sum
jfrom the coin's value up to the target sum:- Update
dp[j]by addingdp[j - coin_value]. This effectively incorporates the ways to form the sumjby including the current coin.
- Update
This method is efficient and elegant. It processes one coin at a time, and for each coin, it updates all sums that can be reached by including it.
Complexity Analysis: The time complexity remains O(n*sum), but the space complexity is reduced to O(sum), which is a significant improvement for large target sums.
Frequently Asked Questions
What is the main difference between the "Ways to Make Sum" and "Minimum Coins" Coin Change problems?
The "Ways to Make Sum" problem counts all distinct combinations of coins that add up to the target amount. The "Minimum Coins" problem finds the smallest number of coins needed to reach that target, which requires a different dynamic programming approach and recurrence relation.
When should I use the recursive solution over the DP solution?
The recursive solution is primarily for educational purposes to understand the problem's structure. It is not efficient for large inputs. For any practical application, always use a Dynamic Programming approach—either top-down or bottom-up—to avoid exponential time complexity.
Why does the space-optimized DP solution use a single array?
The recurrence relation for counting ways relies on the current state and states from the same row (for the same coin). This dependency allows us to overwrite the same array iteratively as we process each coin, eliminating the need to store an entire 2D table.
How does the algorithm handle duplicate combinations?
The standard algorithm counts combinations, not permutations. The order of coins does not matter. This is achieved by processing coins one type at a time. This ensures that once a coin type is processed, all ways to use it are fully accounted for before moving to the next coin, preventing different orders from being counted as distinct solutions.
What happens if the target sum is zero?
According to the problem's definition, there is exactly one way to make a sum of zero: by using no coins at all. This is a crucial base case in all implementations and is why dp[0] is always initialized to 1.
Can this algorithm be applied to real-world currency systems?
Absolutely. This algorithm is directly applicable to any system with fixed denominations, like making change with coins or counting combinations of bills in a cash register. 👉 View real-time tools for financial computation that often implement these principles.