【问题标题】:Solving Dynamic Programming Problem on coins解决硬币上的动态规划问题
【发布时间】:2016-10-25 07:20:08
【问题描述】:

考虑以下问题

给定无限数量的镍币(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 的增长,维护另一个包含先前看到的模式的列表将需要大量内存。我们可以使用什么其他方法来克服这个问题?

【问题讨论】:

  • 如果 n= 便士总数,则 n= (n/5) 镍币 +(n%5) 美分。 = for i= 0 to (n/5), n= (n/5-i) + ((n%5) + (5*i)), 是否可以视为(n/5)+1种方式用镍和美分代表 n 美分。 (或) 如果 n= k 镍和 c 分,则 n=k-(i) + c + 5*i, i 从 0 到 k 变化,即 k+1 种表示 n 分的方式。这看起来很傻吗?
  • 使用此算法无法进一步减少内存。但是,还有其他算法可以解决这个问题。

标签: python recursion dynamic-programming memoization


【解决方案1】:

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

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

我们知道对于任何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 &lt; 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]

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

【讨论】:

  • 我明白你想说的,但我认为你的意思是C[j] &gt; i。我尝试用你的方法实现它,这是我的代码paste.ofcode.org/AmkCtmpYHnK3kPNYLGp84n 但我收到堆栈溢出错误Error: RuntimeError: maximum recursion depth exceeded in comparison
  • @Atinesh 不,C[j] &lt; i 是正确的。如果C[j] &lt; i那么我们不能使用硬币j所以A[i][j] = A[i][j-1]
  • @Atinesh 您的实现在几个方面是错误的。主要问题是您不应该使用递归。由于数组中的每个元素仅依赖于具有较小索引的元素,因此您可以通过对 ij 的值进行简单循环来计算值。
  • 我还是没有得到你。假设 c = [1,3,5],我得到一个电话 A[4][2],即用硬币 {1,3,5} 表示 4 有多少种方法。但是作为c[2] &gt; 4,因此A[4][2] = A[4][1] 即用硬币1,3 表示4 的方式有多少,因为它有硬币5。
  • 这是我最终正确的实现paste.ofcode.org/PDQJbGzrFet2VXYBfMsQiq
【解决方案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}...等计算方式。硬币类型的顺序是固定的,这意味着你不会重复计算任何东西

【讨论】:

  • @Codor 不,我没有最小化它,并且 OP 的问题并没有说它必须使用 DP 来完成(实际上它是贪婪算法,它是 DP 的一个特例)。或者你能提供一个我错过哪个分区的例子吗?
  • 显然,答案中链接下给出的方法是正确的解决方案。原题中的递推关系不正确。
  • @Codor 是的...这是任何硬币系统的通用实现,而我认为对于给定的 {1,5},甚至不需要 DP 解决方案...
  • 遗憾的是,在链接下,没有给出C(N,m) 的语义,但我相信这是仅使用来自(1,...,m) 的硬币来表示N 数量的可能性数量。
  • @shole,在获得的镍币和美分的数量中,我认为它们可能的排列数量是有问题的。
猜你喜欢
  • 2020-09-04
  • 2015-05-08
  • 2020-09-22
  • 1970-01-01
  • 1970-01-01
  • 2011-02-07
  • 2023-03-20
  • 2021-02-25
  • 1970-01-01
相关资源
最近更新 更多