了解变革算法

时间:2013-02-21 00:15:05

标签: python algorithm dynamic-programming

我正在寻找Change-making problem的一个很好的解决方案,我找到了这个代码(Python):

target = 200
coins = [1,2,5,10,20,50,100,200]
ways = [1]+[0]*target
for coin in coins:
    for i in range(coin,target+1):
        ways[i]+=ways[i-coin]
print(ways[target])

我理解代码的字面意思没有问题,但我无法理解为什么它有效。 有人可以帮忙吗?

4 个答案:

答案 0 :(得分:14)

要获得元素等于'a'或'b'或'c'(我们的硬币)总和为'X'的所有可能集合,您可以:

  • 取所有总计为X-a的集合,并为每个集合添加额外的“a”。
  • 取所有总计为X-b的集合,并为每个集合添加额外的“b”。
  • 取所有总计为X-c的集合,并为每个集合添加额外的“c”。

因此,获得X的方式数量是获得X-a和X-b以及X-c的方式数量的总和。

ways[i]+=ways[i-coin]

休息很简单。

额外提示: 在开始时,你可以用一种方式设置零和(空集)

ways = [1]+[0]*target

答案 1 :(得分:10)

这是dynamic programming的经典示例。它使用缓存来避免计算类似事物的陷阱 3 + 2 = 5两次(因为另一种可能的解决方案:2 + 3)。递归算法陷入了陷阱。让我们关注一个简​​单的例子,target = 5coins = [1,2,3]。您发布的这段代码很重要:

  1. 3 + 2
  2. 3 + 1 + 1
  3. 2 + 2 + 1
  4. 1 + 2 + 1 + 1
  5. 1 + 1 + 1 + 1 + 1
  6. 而递归版本计数:

    1. 3 + 2
    2. 2 + 3
    3. 3 + 1 + 1
    4. 1 + 3 + 1
    5. 1 + 1 + 3
    6. 2 + 1 + 2
    7. 1 + 1 + 2
    8. 2 + 2 + 1
    9. 2 + 1 + 1 + 1
    10. 1 + 2 + 1 + 1
    11. 1 + 1 + 2 + 1
    12. 1 + 1 + 1 + 2
    13. 1 + 1 + 1 + 1 + 1
    14. 递归代码:

      coinsOptions = [1, 2, 3]
      def numberOfWays(target):
          if (target < 0):
              return 0
          elif(target == 0):
              return 1
          else:
              return sum([numberOfWays(target - coin) for coin in coinsOptions])
      print numberOfWays(5)
      

      动态编程:

      target = 5
      coins = [1,2,3]
      ways = [1]+[0]*target
      for coin in coins:
          for i in range(coin, target+1):
              ways[i]+=ways[i-coin]
      print ways[target]
      

答案 2 :(得分:4)

代码背后的主要思想如下: “在每一步都有ways种方法可以更改[{1}}给定金币i”的金额。

因此,在第一次迭代中,您只有一个面值为[1,...coin]的硬币。我相信很明显,只有一种方法可以让任何目标只有这些硬币。在此步骤1数组看起来像ways(只有一种方式适用于从[1,...1]0的所有目标。

在下一步中,我们将target面额的硬币添加到上一组硬币中。现在,我们可以计算从20的每个目标的更改组合数量。 显然,组合的数量仅会增加目标&gt; = target(或者通常情况下添加的新硬币)。因此,对于目标等于2,组合的数量将为2 + ways[2](old)。通常,每次ways[0](new)等于引入新硬币时,我们都需要将i添加到之前的组合数组中,其中新组合只包含一枚硬币。

1&gt; target,答案将包含“2金额低于target所有金额的所有组合”和“所有金额低于{1}}金额组合”的总和coin本身“。

这里我描述了两个基本步骤,但我希望很容易概括它。

让我向您展示coincoin的计算树:

  

方式[4]给出硬币= [1,2] =

     

方式[4]给出硬币= [1] +方式[2]给定硬币= [1,2] =

     

1 +方式[2]给定硬币= [1] +方式[0]给定硬币= [1,2] =

     

1 + 1 + 1 = 3

因此,有三种方法可以进行更改:target = 4

上面给出的代码完全等同于递归解决方案。如果您理解the recursive solution,我打赌您理解上面给出的解决方案。

答案 3 :(得分:0)

您发布的解决方案是此代码的摘要版本。

    /// <summary>
    /// We are going to fill the biggest coins one by one.
    /// </summary>
    /// <param name="n"> the amount of money </param>
    public static void MakeChange (int n)
    {
        int n1, n2, n3; // residual of amount after each coin
        int quarter, dime, nickel; // These are number of 25c, 10c, 5c, 1c
        for (quarter = n/25; quarter >= 0; quarter--)
        {
            n1 = n - 25 * quarter;
            for (dime = n1/10; dime >= 0; dime--)
            {
                n2 = n1 - 10 * dime;
                for (nickel = n2/5; nickel >= 0 && (n2 - 5*nickel) >= 0; nickel--)
                {
                    n3 = n2 - 5 * nickel;
                    Console.WriteLine("{0},{1},{2},{3}", quarter, dime, nickel, n3); // n3 becomes the number of cent.
                }
            }
        }
    }