【问题标题】:Understanding change-making algorithm理解变革算法
【发布时间】:2013-02-06 04:41:44
【问题描述】:

我一直在寻找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])

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

【问题讨论】:

标签: python algorithm dynamic-programming


【解决方案1】:

要获得元素等于“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

【讨论】:

    【解决方案2】:

    这是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

    虽然递归版本很重要:

    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

    递归代码:

    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]
    

    【讨论】:

    • 递归方法是错误的。原因很明显。
    • 修复了吗?
    【解决方案3】:

    代码背后的主要思想如下: "在每一步都有ways 的方式来改变i 给硬币的金额[1,...coin]"。

    所以在第一次迭代中,您只有一枚面额为1 的硬币。我相信很明显,只有一种方法可以让任何目标都只使用这些硬币。在这一步中,ways 数组看起来像[1,...1](对于从0target 的所有目标只有一种方式)。

    在下一步中,我们将面额为2 的硬币添加到前一组硬币中。现在我们可以计算从0target 的每个目标有多少变化组合。 显然,组合的数量只会增加目标 >= 2(或添加新硬币,一般情况下)。因此,对于等于2 的目标,组合数将为ways[2](old) + ways[0](new)。通常,每当i 等于引入的新硬币时,我们需要将1 添加到先前的组合数中,其中新组合将仅由一枚硬币组成。

    对于target > 2,答案将由“所有硬币小于cointarget数量的所有组合”和“所有硬币小于coin数量的所有组合”的总和组成coin 自己”。

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

    让我向您展示target = 4coins=[1,2] 的计算树:

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

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

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

    1 + 1 + 1 = 3

    所以有三种方法可以进行更改:[1,1,1,1], [1,1,2], [2,2]

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

    【讨论】:

      【解决方案4】:

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

          /// <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.
                      }
                  }
              }
          }
      

      【讨论】:

        猜你喜欢
        • 2014-12-06
        • 1970-01-01
        • 2023-03-16
        • 2016-05-02
        • 1970-01-01
        • 2011-10-11
        • 1970-01-01
        • 2014-12-15
        • 1970-01-01
        相关资源
        最近更新 更多