简体   繁体   中英

Exact Change Algorithm

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:

  • Find the highest coin in the pool who's value is less than the target amount
  • subtract one of those coins in the pool and subtract its value from the target amount
  • continue until the target amount is 0 (returns true or the list of required coins) or until there is no coin available with a value less than the target (returns false)

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.

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 (eg 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.

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM