【发布时间】:2018-02-15 14:37:51
【问题描述】:
我正在探索动态编程设计方法如何与问题的基本组合属性相关联。
为此,我正在查看硬币找零问题的典型实例:让S = [d_1, d_2, ..., d_m] 和n > 0 成为请求的金额。除了S 中的元素,我们可以通过多少种方式将n 相加?
如果我们遵循动态规划方法来设计一个算法来解决这个问题,该算法将允许具有多项式复杂性的解决方案,我们将首先查看问题以及它与更小和更小的问题之间的关系。更简单的子问题。这将产生一个递归关系,描述一个归纳步骤,根据其相关子问题的解决方案来表示问题。然后,我们可以实现 memoization 技术或 tabulation 技术,以分别以自上而下或自下而上的方式有效地实现这种递归关系。
解决此问题实例的递归关系可能如下(Python 3.6 语法和基于 0 的索引):
def C(S, m, n):
if n < 0:
return 0
if n == 0:
return 1
if m <= 0:
return 0
count_wout_high_coin = C(S, m - 1, n)
count_with_high_coin = C(S, m, n - S[m - 1])
return count_wout_high_coin + count_with_high_coin
这种递归关系产生了正确数量的解决方案,但忽略了顺序。但是,这种关系:
def C(S, n):
if n < 0:
return 0
if n == 0:
return 1
return sum([C(S, n - coin) for coin in S])
在考虑订单时产生正确数量的解决方案。
我有兴趣通过可以通过记忆/制表进一步优化的递归关系来捕捉更微妙的组合模式。
比如这个关系:
def C(S, m, n, p):
if n < 0:
return 0
if n == 0 and not p:
return 1
if n == 0 and p:
return 0
if m == 0:
return 0
return C(S, m - 1, n, p) + C(S, m, n - S[n - 1], not p)
产生一个不考虑顺序的解决方案,但只计算具有偶数个被加数的解决方案。同样的关系可以修改为关于偶数和数的顺序和计数:
def C(S, n, p):
if n < 0:
return 0
if n == 0 and not p:
return 1
if n == 0 and p:
return 0
return sum([C(S, n - coin, not p) for coin in S])
但是,如果我们想要在其中分割硬币的人超过 1 人怎么办?假设我想将n 分成 2 个人。每个人得到相同数量的硬币,无论每个人得到的总和是多少。在 14 个解决方案中,只有 7 个包含偶数个硬币,这样我就可以平均分配它们。但我想排除给每个人多余的硬币分配。例如,1 + 2 + 2 + 1 和 1 + 2 + 1 + 2 在订单很重要时是不同的解决方案,但它们代表相同的硬币分成两个人,即人 B 将得到 1 + 2 = 2 + 1。我很难想出一个递归来以非冗余方式计算拆分。
【问题讨论】:
-
您正在尝试解决partition problem。引用的参考文献给出了一种基于动态规划的伪多项式算法。请注意,该问题实际上是 NP 完全问题(理论运行时间是所涉及数字的函数)。
-
@collapsar 我不认为这是分区问题。 OP 声明,“我想将 n 分成 2 个人,每个人得到相同数量的硬币,不管每个人得到的总和是多少。”
-
添加了自下而上的实现。这会在大约 2 秒内生成
f(500, [1, 2, 6, 12, 24, 48, 60])的答案。 -
我认为在忽略顺序的情况下计算偶数和数的代码有错误......不应该读成
return C(S, m - 1, n, p) + C(S, m, n - S[m - 1], not p)吗? IE。n - S[m - 1]而不是n - S[n - 1]?
标签: algorithm recursion dynamic-programming recurrence coin-change