解决硬币上的动态规划问题

时间:2016-06-23 06:02:24

标签: python recursion dynamic-programming memoization

考虑以下问题

  

给予无限数量的镍(5美分)和便士(1美分)。编写一个代码来计算代表n美分的多种方式。

我的代码

def coins(n):
    if (n < 0):
        return 0
    elif (n == 0):
        return 1
    else:
        if (cnt_lst[n-1] == 0):
            cnt_lst[n-1] = coins(n-1) + coins(n-5)
        return cnt_lst[n-1]

if __name__ == "__main__":
    cnt = int(input())
    cnt_lst = [0] * cnt #Memiozation
    ret = coins(cnt)
    print(ret)

以上方法计算不止一个重复模式(显然我没有明确地检查它们)。

  

[5,1,1,1,1,1] [1,5,1,1,1,1] [1,1,5,1,1,1]等

维护另一个包含先前看到的模式的列表,随着n的增长需要大量内存。我们可以用另一种方法来解决这个问题吗?

2 个答案:

答案 0 :(得分:2)

虽然你的实现是典型的Coin Change Problem的扭曲,1维度更少,这可能会导致你重复计算很多组合,我认为给定的硬币系统并不复杂。

我认为解决方案只是 floor(N/5) + 1

由于你只能使用5美分的[0..N/5],剩下的其他你必须使用1美分

这可以很容易地完成,因为您的硬币系统是规范的,并且可以应用贪婪的算法/思维模式。

如果您坚持使用动态编程来解决问题,则需要添加一个维度,表示正在使用的硬币类型。这种方法适用于任何硬币系统

定义C(x, m):= The # of ways to make number x using first m type of coins

所以现在递推公式变为:

C(x, m) = C(x-coin_type[m], m) + C(x, m-1),表示您选择使用第m种硬币,或不使用它

以下是主要观点,即为什么这种重复发生并且你的工作不起作用,状态的迭代次序

使用此递推公式,我们可以执行类似

的操作
For i = 0 to # of coin_type
    For j = 0 to n
       C(j, i) = C(j-coin_type[i], i) + C(j, i-1)

注意外环强制硬币类型的迭代排序。假设coin_type = {1,3,5},循环将首先计算仅使用{1}的方式,然后循环的下一次迭代将计算使用{1,3}的方式,最后一次迭代将使用{1计算方式, 3,5}。

你永远不会计算使用{3,1,5}或{1,5,3}等的方式。硬币类型的顺序是固定的,这意味着你不会重复计算任何东西

答案 1 :(得分:2)

您可以使用二维数组,而不是使用一维列表,其中一维是总数,第二维是可用的最大值硬币。

假设C是您按升序排列的硬币值列表(在您的示例中为C = [1, 5])。然后,A[i][j]是用硬币i0来表示价值j的方式的数量。

我们知道,对于任何jA[0][j] = 1,因为只有一种方法可以表示值0:没有硬币。

现在假设我们想要找到A[8][1],用便士和镍币代表8美分的方式。每个表示将使用镍或不会。如果我们不使用镍,那么我们只能使用便士,所以有A[8][0]方法可以做到这一点。如果我们确实使用了镍,那么我们剩下3美分,因此有A[3][1]个方法可以做到这一点。

要计算A[8][0]我们只有一枚可用的硬币A[8][0] = A[7][0] = ... = A[0][0] = 1

要计算A[3][1],我们无法使用3 < 5以来的镍,因此A[3][1] = A[3][0]。从那里我们有A[3][0] = A[2][0] = ... = 1,如上所述。

一般来说:

A[i][j] = A[i][j-1] if i < C[j]
          else A[i][j-1] + A[i-C[j]][j]

此算法适用于任何一组硬币值。