Given an amount N and an infinite supply of coins of denominations stored in list S = {S1, S2, ..., Sm}, the task is to find how many different ways we can make change for the amount N. The order of coins doesn’t matter only combinations count.
For Example:
Input: N = 4, S = [1, 2, 3]
Output: 4
Possible combinations: {1,1,1,1}, {1,1,2}, {2,2}, {1,3}Input: N = 10, S = [2, 5, 3, 6]
Output: 5
Possible combinations: {2,2,2,2,2}, {2,2,3,3}, {2,2,6}, {2,3,5}, {5,5}
Let's explore different methods to solve this.
Dynamic Programming (1D Table)
In this method, we use a 1D table to count combinations. It iteratively updates the number of ways to make each amount using available coins, building results from smaller subproblems.
S = [1, 2, 3]
n = 4
t = [0] * (n + 1)
t[0] = 1
for coin in S:
for i in range(coin, n + 1):
t[i] += t[i - coin]
print(t[n])
Output
4
Explanation:
- t keeps track of ways to make each value.
- For every coin, we add the ways to make the remaining amount (i - coin).
- t[0] = 1 represents one valid way (choosing no coin).
- The final answer is stored in t[n].
Recursion with Memoization (Top-Down DP)
In this method, the problem is solved recursively but results of subproblems are stored in a dictionary (memoization).
S = [1, 2, 3]
n = 4
memo = {}
def helper(amt, idx):
if (amt, idx) in memo:
return memo[(amt, idx)]
if amt == 0:
return 1
if amt < 0 or idx >= len(S):
return 0
memo[(amt, idx)] = helper(amt - S[idx], idx) + helper(amt, idx + 1)
return memo[(amt, idx)]
print(helper(n, 0))
Output
4
Explanation:
- Recursively includes and excludes each coin.
- (amt, idx) acts as a unique subproblem key.
- Results are stored in memo to prevent recomputation.
- Returns total valid combinations.
Dynamic Programming (2D Table)
In this method, a 2D table is used where each cell stores the number of ways to make a certain amount using a specific number of coin types.
S = [1, 2, 3]
m = len(S)
n = 4
t = [[0 for _ in range(m)] for _ in range(n + 1)]
for i in range(m):
t[0][i] = 1
for i in range(1, n + 1):
for j in range(m):
x = t[i - S[j]][j] if i - S[j] >= 0 else 0
y = t[i][j - 1] if j >= 1 else 0
t[i][j] = x + y
print(t[n][m - 1])
Output
4
Explanation:
- x: ways including the current coin.
- y: ways excluding the current coin.
- Combines both to build up total combinations.
- The bottom-right cell stores the final answer.
Brute Force (Recursive Approach)
This method tries every possible combination of coins recursively without optimization. It’s easy to implement but inefficient for large inputs.
S = [1, 2, 3]
n = 4
def count(coins, m, N):
if N == 0:
return 1
if N < 0:
return 0
if m <= 0 and N >= 1:
return 0
return count(coins, m - 1, N) + count(coins, m, N - coins[m - 1])
print(count(S, len(S), n))
Output
4
Explanation:
- Tries two cases for each coin, include or exclude.
- Recursively explores all combinations.
- Works well for small inputs but grows exponentially for large values.
Please refer complete article on Dynamic Programming | Set 7 (Coin Change) for more details!