Reputation: 13
I'm trying to write an algorithm that checks if it is possible to get exact change given a limited number of coins of different values, specifically Quarters, Dimes, Nickels, and Pennies. I have seen many proposed solutions including here on stack overflow as well as one from freeCodeCamp but both of these solutions share the same problem in that sometimes produce false negatives.
NOTE: this is not the Change-making problem as I am not interested in the minimum number of coins from an infinite pool, just if the existing pool can support an exact quantity.
The algorithm I have seen commonly used is as follows:
But there is a problem with this approach: If a solution requires a high value of low denomination coins, it will return false even though there may be a valid solution.
Here is some code of this algorithm (adapted for my purposes from that stack overflow article linked earlier so it may be messy)
values = [25, 10, 5, 1]
def exact_change(target_amount, L, ret = None):
if ret == None:
ret = [0] * len(L)
for i in range(len(L)):
if L[i] == 0:
continue
if values[i] == target_amount:
ret[i] += 1
return ret
else:
if values[i] < target_amount:
L[i] -= 1
ret[i] += 1
return exact_change(target_amount-values[i], L, ret)
else:
return False
print(exact_change( 48, [1, 2, 6, 3] ))
# [1, 2, 0, 3] correct
print( exact_change( 45, [2, 1, 1, 4] ))
# False correct
print(exact_change( 285, [100, 10, 10, 100] ))
# [11, 1, 0, 0] correct
print(exact_change( 65, [2, 4, 1, 0] ))
# [2, 1, 1, 0] correct
print(exact_change( 65, [2, 6, 0, 0] ))
# False incorrect! [1, 4, 0, 0] would work
This approach doesn't work if you start by looking for lower valued coins first either. Is there a better algorithm I simply haven't found yet or is this an open problem? I can brute force a solution by checking each combination of coins that would produce the target value and seeing if that combination is possible with the given pool but that seems inefficient for large values.
Upvotes: 1
Views: 4076
Reputation: 13
Spring-boarding off of Blckkngt's solution, I found a solution that works (although it could be significantly optimized by dynamic programming and memoization).
values = [25, 10, 5, 1]
def exact_change(target_amount, pool, coin_index = 0, ret = None):
if coin_index == len(pool):
return None
if ret == None:
ret = [0] * len(pool)
#count down from the maximum number of coins that can fit in the target_amount
# or the maximum of that coin available in the pool
for current_amount in range(min(target_amount//values[coin_index], pool[coin_index]), -1, -1):
ret[coin_index] = current_amount
if target_amount - current_amount * values[coin_index] == 0:
return ret
exact = exact_change(target_amount - current_amount * values[coin_index], pool, coin_index + 1, ret)
if exact:
return exact
else:
return None
It is effectively the same as Blckkngt's code but it allows for backtracking more than just the last step. Although, I realize that without storing results like a dynamic coding solution would, this is basically just a recursive brute force. Regardless, This fits my purpose and works great!
Big thanks to everyone for helping out!
Upvotes: 0
Reputation: 104722
The problem your current algorithm has is that it only ever attempts to recurse once. Regardless of what that recursion finds (either a valid solution, or a failure), that result is returned unconditionally. While there are some other algorithms that would work differently, you can make a small change to your current code to make it work by adding backtracking.
The backtracking step happens after each recursion. First you check if the recursion was successful (e.g. it found a valid result). If so, you return that result like normal. However, if it was unsuccessful, you need to undo the changes you made to the problem state (in this case, undo the changes to L
and ret
, and then go on to try another coin denomination.
There's another issue with your code though. Your code doesn't have any way to avoid checking the same starting denominations every time your function recurses, so for some inputs you'll keep trying the same bad solutions over and over. A way to avoid reexamining the same coins is to pass a start index along with the other arguments, and not look at the coins before that index at all.
def exact_change(target_amount, L, start_index=0, ret=None):
if ret == None:
ret = [0] * len(L)
for i in range(start_index, len(L)): # start at start_index
if L[i] == 0:
continue
if values[i] == target_amount:
ret[i] += 1
return ret
elif values[i] < target_amount: # no need for the extra indentation of else: if ...:
L[i] -= 1
ret[i] += 1
result = exact_change(target_amount-values[i], L, i, ret) # pass i as start_index
if result: # check the result to make sure it was successful before returning
return result
L[i] += 1 # backtrack if it was not
ret[i] -= 1
else:
return False # None might be a more natural value to return here
Note that if you wanted to, you could avoid modifying L
during the recursive step (and needing to undo the change when backtracking). Just change the L[i] == 0
test to L[i] == ret[i]
instead.
Upvotes: 3