n
coin denominations and a total money amount, we are asked to find the minimum number of coins needed to make up that amount.Example 1
Denominations: {1,2,3}
Total amount: 5
Output: 2
Explanation: We need a minimum of two coins {2,3} to make a total of '5'
Example 2
Denominations: {1,2,3}
Total amount: 11
Output: 4
Explanation: We need a minimum of four coins {2,3,3,3} to make a total of '11'
T
, we need to find the minimum number of coins needed to make a change for T
. We can assume an infinite supply of coins, therefore, each coin can be chosen multiple times.A basic brute-force solution could be to try all combinations of the given coins to select the ones that give a total sum of T
. This is what our algorithm will look like:
for each coin 'c'
create a new set which includes one quantity of coin 'c' if it does not exceed 'T', and
recursively call to process all coins
create a new set without coin 'c', and recursively call to process the remaining coins
return the count of coins from the above two sets with a smaller number of coins
Here is the code for the brute-force solution:
import math
def count_change(denominations, total):
result = count_change_recursive(denominations, total, 0)
return -1 if result == math.inf else result
def count_change_recursive(denominations, total, currentIndex):
# base check
if total == 0:
return 0
n = len(denominations)
if n == 0 or currentIndex >= n:
return math.inf
# recursive call after selecting the coin at the currentIndex
# if the coin at currentIndex exceeds the total, we shouldn't process this
count1 = math.inf
if denominations[currentIndex] <= total:
res = count_change_recursive(
denominations, total - denominations[currentIndex], currentIndex)
if res != math.inf:
count1 = res + 1
# recursive call after excluding the coin at the currentIndex
count2 = count_change_recursive(denominations, total, currentIndex + 1)
return min(count1, count2)
def main():
print(count_change([1, 2, 3], 5))
print(count_change([1, 2, 3], 11))
print(count_change([1, 2, 3], 7))
print(count_change([3, 5], 7))
main()
Let’s try to find a better solution.
We can use memoization to overcome the overlapping sub-problems. We will be using a two-dimensional array to store the results of solved sub-problems. As mentioned above, we need to store results for every coin combination and for every possible sum:
Here is the code:
import math
def count_change(denominations, total):
dp = [[-1 for _ in range(total+1)] for _ in range(len(denominations))]
result = count_change_recursive(dp, denominations, total, 0)
return -1 if result == math.inf else result
def count_change_recursive(dp, denominations, total, currentIndex):
# base check
if total == 0:
return 0
n = len(denominations)
if n == 0 or currentIndex >= n:
return math.inf
# check if we have not already processed a similar sub-problem
if dp[currentIndex][total] == -1:
# recursive call after selecting the coin at the currentIndex
# if the coin at currentIndex exceeds the total, we shouldn't process this
count1 = math.inf
if denominations[currentIndex] <= total:
res = count_change_recursive(
dp, denominations, total - denominations[currentIndex], currentIndex)
if res != math.inf:
count1 = res + 1
# recursive call after excluding the coin at the currentIndex
count2 = count_change_recursive(
dp, denominations, total, currentIndex + 1)
dp[currentIndex][total] = min(count1, count2)
return dp[currentIndex][total]
def main():
print(count_change([1, 2, 3], 5))
print(count_change([1, 2, 3], 11))
print(count_change([1, 2, 3], 7))
print(count_change([3, 5], 7))
main()
Let’s try to populate our array dp[TotalDenominations][Total+1]
for every possible total with a minimum number of coins needed.
So for every possible total t
(0<= t <= Total) and for every possible coin index (0 <= index < denominations.length), we have two options:
Exclude the coin: In this case, we will take the minimum coin count from the previous set => dp[index-1][t]
Include the coin if its value is not more than t
: In this case, we will take the minimum count needed to get the remaining total, plus include 1
for the current coin => dp[index][t-denominations[index]] + 1
Finally, we will take the minimum of the above two values for our solution:
dp[index][t] = min(dp[index-1][t], dp[index][t-denominations[index]] + 1)
Let’s draw this visually with the following example:
Denominations: [1, 2, 3]
Total: 7
Let’s start with our base case of zero total:
Here is the code for our bottom-up dynamic programming approach:
import math
def count_change(denominations, total):
n = len(denominations)
dp = [[math.inf for _ in range(total+1)] for _ in range(n)]
# populate the total=0 columns, as we don't need any coin to make zero total
for i in range(n):
dp[i][0] = 0
for i in range(n):
for t in range(1, total+1):
if i > 0:
dp[i][t] = dp[i - 1][t] # exclude the coin
if t >= denominations[i]:
if dp[i][t - denominations[i]] != math.inf:
# include the coin
dp[i][t] = min(dp[i][t], dp[i][t - denominations[i]] + 1)
# total combinations will be at the bottom-right corner.
return -1 if dp[n - 1][total] == math.inf else dp[n - 1][total]
def main():
print(count_change([1, 2, 3], 5))
print(count_change([1, 2, 3], 11))
print(count_change([1, 2, 3], 7))
print(count_change([3, 5], 7))
main()
✓→ Maximum Ribbon Cut