精确变更算法

时间:2017-04-15 22:07:40

标签: python algorithm

我正在尝试编写一种算法,检查是否可以在给定数量有限的不同值的硬币(特别是四分之一,角钱,镍和便士)的情况下获得准确的更改。我已经看到了许多提议的解决方案including here on stack overflow以及freeCodeCamp中提出的解决方案,但这两种解决方案都有同样的问题,因为它们有时会产生漏报。

注意:这不是Change-making problem,因为我对无限池中的最小硬币数量不感兴趣,只是现有的池可以支持确切的数量。

我见过的常用算法如下:

  • 在池中找到价值低于目标金额的最高硬币
  • 从池中扣除其中一个硬币并从目标金额中减去其值
  • 继续,直到目标金额为0(返回true或所需硬币列表)或直到没有可用的值小于目标的硬币(返回false)

但这种方法存在问题:如果解决方案需要高价值的低面额硬币,即使可能存在有效的解决方案,它也会返回假。

以下是此算法的一些代码(适用于我之前关联的堆栈溢出文章的目的,因此可能会很混乱)

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

如果您先从寻找较低价值的硬币开始,这种方法不起作用。有没有更好的算法我还没有找到或者这是一个开放的问题?我可以通过检查能够产生目标值的每个硬币组合并查看是否可以与给定池组合来强制解决方案,但对于大值来说这似乎效率低下。

2 个答案:

答案 0 :(得分:3)

您当前的算法存在的问题是它只尝试递归一次。无论递归发现什么(无论是有效解决方案还是失败),都会无条件地返回该结果。虽然还有一些其他算法的工作方式不同,但您可以对当前代码进行一些小改动,以便通过添加回溯来使其工作。

每次递归后都会发生回溯步骤。首先检查递归是否成功(例如,它找到了有效的结果)。如果是这样,您可以像平常那样返回结果。但是,如果不成功,则需要撤消对问题状态所做的更改(在这种情况下,撤消对Lret的更改,然后继续尝试另一个硬币面额。

但是你的代码还有另一个问题。您的代码没有任何方法可以避免每次函数递归时都检查相同的起始面额,因此对于某些输入,您将一遍又一遍地尝试相同的错误解决方案。避免重新检查相同硬币的一种方法是将起始索引与其他参数一起传递,而不是在该索引之前查看硬币。

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

请注意,如果您愿意,可以避免在递归步骤中修改L(并且需要在回溯时撤消更改)。只需将L[i] == 0测试更改为L[i] == ret[i]

答案 1 :(得分:0)

Blckkngt解决方案的春季登陆,我发现了一个有效的解决方案(虽然它可以通过动态编程和记忆来显着优化)。

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

它实际上与Blckkngt的代码相同,但它允许回溯不仅仅是最后一步。虽然,我意识到如果不像动态编码解决方案那样存储结果,这基本上只是一种递归的蛮力。无论如何,这符合我的目的并且效果很好!

非常感谢大家的帮助!