-
Recursive approach: This approach solves the problem by exploring all possible combinations of coin denominations recursively. It checks each coin to see if it can contribute to the solution, which leads to a lot of redundant calculations.
-
Dynamic programming approach: This method optimizes the recursive approach by storing the results of previously solved subproblems in a
dp[]array. It avoids redundant calculations, making it much more efficient.
Minimum coin change in C++ - a dynamic programming question
Key takeaways:
Optimal solution through dynamic programming: The dynamic programming approach efficiently solves the coin change problem by avoiding redundant calculations, reducing time complexity to
. Recursive solution’s limitations: The recursive approach has exponential time complexity
, making it inefficient for larger inputs due to overlapping subproblems. Memoization for optimization: Using memoization in the recursive approach (top-down dynamic programming) reduces redundant calculations, improving efficiency and reducing time complexity to
. Real-world applicability: This problem models scenarios like currency exchange, resource allocation, and vending machines, where optimal combinations of available denominations must be found to meet a target amount.
Commonly asked in interviews: The coin change problem is frequently posed in technical interviews, especially for software engineering roles, as it tests problem-solving skills and proficiency in recursion, dynamic programming, and algorithm optimization.
What is the minimum coin change?
The minimum coin change problem is a classic problem in which you are given a total amount of money and a set of coin denominations. The goal is to determine the minimum number of coins needed to make up the given amount. If it is not possible to achieve the target amount using the available denominations, the solution should return
Problem statement
Given an integer total representing the target amount of money and a list of integers coins representing coin denominations, determine the minimum number of coins required to achieve the target. If it’s not possible to form the target using the given coins, return
Note: You can assume that we have an infinite number of each kind of coin.
Constraints:
coins.lengthcoins[i]total
Examples
Knowledge test
Let’s take a moment to ensure you’ve correctly understood the problem. The quiz below helps you check if you’re solving the correct problem.
Maximum subarray sum
What is the minimum number of coins required to make a total amount of 11 using coin denominations [1, 2, 3, 4, 5]?
2
3
4
5
Solution to find minimum coin change
The coin change problem can be solved using two different approaches:
Recursive approach
Dynamic programming approach
We’ll take a look at both of these approaches in detail, along with their code examples and explanations.
1. Recursive approach
The recursive approach tries all combinations of coins to reach the target amount, reducing the target by each coin value in every step. It finds the minimum coins required but is inefficient due to repeated calculations, leading to exponential complexity.
Here are the detailed steps of the algorithm discussed above:
We iterate through each coin in the
coinsarray:If a coin’s value is less than or equal to the remaining target (
N), we make a recursive call for the new targetN - coin.We calculate the minimum number of coins required by taking the minimum of all possible subproblem results, adding
1for the current coin used.
The recursive function returns the minimum number of coins if a solution exists; otherwise, it returns
-1.
Let’s look at the example code for the recursive approach:
#include <bits/stdc++.h>using namespace std;int minCoins(int coins[], int m, int N) {if (N == 0)return 0;// Initialize result to a large valueint res = INT_MAX;for (int i = 0; i < m; i++) {// Check if the coin can be used (coin value <= remaining target)if (coins[i] <= N) {// Recursive call for the reduced amountint sub_res = minCoins(coins, m, N - coins[i]);// Update the result if a smaller value is foundif (sub_res != INT_MAX) // Ensure no overflowres = min(res, 1 + sub_res);}}return res;}int main() {struct TestCase {vector<int> coins; // Coin denominationsint target; // Target amount};TestCase testCases[] = {{{1, 2, 3, 4, 5}, 11},{{2, 5, 10}, 7},{{1}, 3},{{5, 10, 25}, 30},{{3, 7}, 5}};int totalTests = sizeof(testCases) / sizeof(testCases[0]);for (int i = 0; i < totalTests; i++) {TestCase tc = testCases[i];int m = tc.coins.size();int result = minCoins(tc.coins.data(), m, tc.target);cout << "Test case " << i + 1 << ":\n";cout << "\tCoins: [";for (int j = 0; j < tc.coins.size(); j++) {cout << tc.coins[j];if (j != tc.coins.size() - 1)cout << ", ";}cout << "]\n";cout << "\tTarget: " << tc.target << "\n";cout << "\tMinimum coins required: " << (result == INT_MAX ? -1 : result) << "\n";cout << string(100, '-') << "\n";}return 0;}
Complexity analysis
Time complexity: The time complexity of the recursive approach is
Space complexity: The space complexity is
2. Dynamic programming approach
The dynamic programming approach uses a dp[] array to store the minimum coins needed for each amount. It builds solutions iteratively, avoiding redundant calculations and making it more efficient.
Here are the detailed steps of the algorithm discussed above:
We create a
dp[]array of sizeN + 1wheredp[i]stores the minimum number of coins needed to make up the sumi.Initialize all entries of
dp[]toINT_MAXexceptdp[0], which is0because no coins are needed to make a sum of0.
We loop through all possible target sums from
1toN:For each target sum, we check every coin in the
coinsarray.If the coin’s value is less than or equal to the current sum, we update
dp[i]using:
After iterating through all sums, the value of
dp[N]contains the minimum number of coins needed to make up the target amountN. Ifdp[N]remainsINT_MAX, it means the target cannot be achieved, and we return-1.
Let’s look at the example code for the recursive approach:
#include <bits/stdc++.h>using namespace std;int minCoins(int coins[], int M, int N) {vector<int> dp(N + 1, INT_MAX);dp[0] = 0;// Fill the dp array for all amounts from 1 to Nfor (int i = 1; i <= N; i++) {for (int j = 0; j < M; j++) {if (coins[j] <= i) {if (dp[i - coins[j]] != INT_MAX) {dp[i] = min(dp[i], 1 + dp[i - coins[j]]);}}}}// If dp[N] is still INT_MAX, no solution existsreturn dp[N] == INT_MAX ? -1 : dp[N];}int main() {struct TestCase {vector<int> coins; // Coin denominationsint target; // Target amount};TestCase testCases[] = {{{1, 2, 3, 4, 5}, 11},{{2, 5, 10}, 7},{{1}, 3},{{5, 10, 25}, 30},{{3, 7}, 5}};int totalTests = sizeof(testCases) / sizeof(testCases[0]);for (int i = 0; i < totalTests; i++) {TestCase tc = testCases[i];int m = tc.coins.size();int result = minCoins(tc.coins.data(), m, tc.target);cout << "Test case " << i + 1 << ":\n";cout << "\tCoins: [";for (int j = 0; j < tc.coins.size(); j++) {cout << tc.coins[j];if (j != tc.coins.size() - 1)cout << ", ";}cout << "]\n";cout << "\tTarget: " << tc.target << "\n";cout << "\tMinimum coins required: " << result << "\n";cout << string(100, '-') << "\n";}return 0;}
Complexity analysis
Time complexity: The time complexity of the dynamic programming approach is
is the number of coin denominations, is the target amount.
Space complexity: The space complexity is dp[] of size N + 1 to store the minimum number of coins required for each sum from 0 to N.
Comparison of both approaches
Aspect | Recursive | Dynamic Programming |
Approach | Top-down, repeatedly solving subproblems | Bottom-up, solving subproblems iteratively |
Time complexity | O(2^N), exponential due to overlaps | O(M⋅N), efficient |
Space complexity | O(N) for recursion stack | O(N) for the |
Optimal for large value of | No | Yes |
Additional resources
To learn more about data structures and algorithms and practice more LeetCode-based problems such as the Coin Change problem. Look at the following list of curated courses at Educative:
Frequently asked questions
Haven’t found what you were looking for? Contact Us