【问题标题】:How to solve coin change task when order does matters?订单重要时如何解决硬币找零任务?
【发布时间】:2014-09-10 19:01:44
【问题描述】:

正如我在here 中发现的,

硬币找零问题是使用给定的一组面额 d_1....d_m 找出特定数量的美分 n 找零的方法的数量。是整数分区的一般情况,可以通过动态规划求解。

这个问题通常被问为:如果我们想找 N 美分,并且我们有无限供应 S = { S_1, S_2,....., S_m } 价值硬币,有多少种方法可以我们做出改变? (为简单起见,顺序无关紧要。)

我试过了,效果很好。那么当不同硬币的顺序确实很重要时,我如何修改它以找到所有可能的硬币组合。

即: 之前

例如对于 N = 4,S = {1,2,3},有四种解:{1,1,1,1},{1,1,2},{2,2}, {1,3}。

现在:

对于 N = 4,S = {1,2,3},有 7 个解:{1,1,1,1},{1,1,2},{1,2,1},{ 2,1,1},{2,2},{1,3},{3,1}

这里是 {1,1,1,1},即使一个人可以以不同的顺序选择四个 '1',它也必须被视为一个最终组合。而不是考虑四个硬币是不同的。所以实际上不同硬币的顺序必须不同才能算作单独的组合。

例如:{1,1,3} 不会假定 {1_a,1_b,3_a} 是一个组合,而 {1_b,1_a,3_a} 是另一个具有不同顺序的组合。

【问题讨论】:

  • 您可以使用初始解决方案生成唯一集,然后生成每个集的所有排列以生成所有唯一序列。
  • 你真的想要所有的解决方案还是有多少?
  • @Henry 他们俩都可以。我想找到号码。 (如示例中为 7 )。如果我也可以打印它们,它不会造成伤害。
  • @mbeckish:是的。如果我知道每个解决方案的不同硬币值的数量和它们在解决方案中出现的时间,这很容易。但是使用链接中给定的算法,他们将只计算答案(不同解决方案的总和)。 (不是一一不同的解决方案)

标签: java algorithm


【解决方案1】:

只计算解决方案的数量比列举所有解决方案的工作量要少得多。

我们以 S={1,2,3} 为例,将 f(n) 称为数量 n 的解数。

那么我们有:

f(n) = 0 如果 n

f(0) = 1

f(n) = f(n-1) + f(n-2) + f(n-3) 如果 n > 0(其中数字 1,2,3 是 S 的元素)

编写执行这些计算的程序并不难。您可以从较低的数字开始,然后逐步提高:

f(0) = 1
f(1) = 1
f(2) = 2
f(3) = 4
f(4) = 7
f(5) = 13
...

对于这个特定的 S,事实证明每个数字只是前面三个数字的总和。

如何得出这个公式?我再次以特定集合 S={1,2,3} 为例,一般情况同样容易。要计算 n 的解决方案数量,请查看第一个元素。它可以是 1、2 或 3。如果是 1,则有 f(n-1) 种方式来排列剩余元素。如果是 2,则剩余元素有 f(n-2) 种方式,最后如果是 3,则剩余元素有 f(n-3) 种方式。因此,总数必须是三者之和。

【讨论】:

  • 您正在使用此递归解决方案计算重复值。即 {2,2} 和 {2,2} 应该是 1 排列而不是 2。您的方法仅适用于不同的元素。
  • @wckd 不正确,如果是这种情况,f(4) 的值将不正确。
  • 假设这是正确的,如果 S 是动态分配的。我们可能事先不知道 S。
  • @wckd 我不认为这是正确的。计算 f(4) 时使用的术语之一是 f(4-2),它基本上计算所有以 2 开头的序列,然后是所有加起来为 2 的序列。因为他对 f(2) 的计数不包括序列 {2} 两次,我看不出 f(4) 的计数如何包括 {2,2} 两次。
  • @prime 我认为他的意思是 f(n) = f(n-S_1) + f(n-S_2) + ... + f(n-S_m)。他关于“前面三个数字”的最后陈述不适用于一般情况。
【解决方案2】:

如果您指的是引用的维基百科页面中的“动态编程”算法,我认为您可以更改

table[ i, j ] = table[ i - S_j, j ] + table[ i, j - 1 ]

table[ i, j ] = table[ i - S_j, m ] + table[ i, j - 1 ]

但我还不能 100% 确定。关键是在原始问题中,当您检查硬币 Sj 时,您希望在数量为 i - Sj 时添加可能解决方案的数量,但是只有通过 Sj 的硬币,这样你就不会得到前一个序列的排列。通过将其更改为table[i - S_j, m],您可以计算排列。

编辑: 进一步研究后,我相信这是正确的,但等同于亨利的答案,这要简单得多。这个版本的问题(计算所有排列)不需要像原始数组那样将值存储在二维数组中。

【讨论】:

  • 那么用这种方法会成立吗? {1,1,3} 不会假定 {1_a,1_b,3_a} 是一个组合,而 {1_b,1_a,3_a} 是另一个具有不同顺序的组合
  • @prime 如果你问它是否会只计算一次 {1,1,3},我想是的。但我还没有测试过。
  • 是的。这就是我要问的。这是一个主要问题。感谢您的回答。如果您有解决方案,请贡献或请确认。 :)
【解决方案3】:

当前的递归实现非常简单。它只传递剩余价值和可能面额的索引。如果您在到达 n==0 时传递了一个面额数组,您可以添加另一个检查以查看您是否已经拥有该组合。如果确实返回 0,否则返回 1。

这是一个动态解决方案,需要比其他解决方案更多的内存,但它会为您提供答案。

func count(n, m, p[])
//regular if checks with additional nested if in (n == 0)
return count( n, m - 1, p[] ) + count( n - S[m], 0, p[] + S[m] )

在这个伪代码中 p[] + S[m] 只是意味着将 S[m] 添加到 p[] 中的下一个可用位置

编辑:

忘记补充说下潜时需要重置m

// n is remaining value
// m is index of S array
// p[] is array of coins used
// solutions[] is an array of p[] arrays
func count( n, m, p[]) {
  if n == 0
    if (p[] not in solutions[]){
      solutions[].add(p[])
      return 1
    }else{
      return 0
    }
  if n < 0
    return 0
  if m <= 0 and n >= 1
    return 0
  return count(n, m - 1, p[]) + count( n - S[m], 0, p[].add(S[m]))
}

每次您将硬币添加到可能的集合时,都从一个空数组 p[] 开始,您将该值附加到数组中。当您找到解决方案时,您会看到它是否是独一无二的。如果是这样,您将其添加到计数中,否则您将忽略它。 因为 m 每次都会重置,它会遍历所有可能的排列。

【讨论】:

  • 你能解释一下这个方法吗?
猜你喜欢
  • 2015-09-02
  • 1970-01-01
  • 2015-03-08
  • 1970-01-01
  • 2020-12-16
  • 1970-01-01
  • 1970-01-01
  • 2022-01-16
  • 2013-12-09
相关资源
最近更新 更多