简体   繁体   English

动态编程最佳找零

[英]Dynamic Programming Optimal Coin Change

I've been reviewing some dynamic programming problems, and I have had hard time wrapping my head around some code in regards to finding the smallest number of coins to make change. 我一直在审查一些动态编程问题,并且在寻找最小数量的硬币来进行更改方面,我一直费时费力。

Say we have coins worth 25, 10, and 1, and we are making change for 30. Greedy would return 25 and 5(1) while the optimal solution would return 3(10). 假设我们有价值分别为25、10和1的硬币,我们将零钱更改为30。贪婪将返回25和5(1),而最优解将返回3(10)。 Here is the code from the book on this problem: 这是本书中有关此问题的代码:

def dpMakeChange(coinValueList,change,minCoins):
   for cents in range(change+1):
      coinCount = cents
      for j in [c for c in coinValueList if c <= cents]:
            if minCoins[cents-j] + 1 < coinCount:
               coinCount = minCoins[cents-j]+1
      minCoins[cents] = coinCount
   return minCoins[change]

If anyone could help me wrap my head around this code (line 4 is where I start to get confused), that would be great. 如果有人可以帮助我解决这段代码(我开始感到困惑的第4行),那将很棒。 Thanks! 谢谢!

It looks to me like the code is solving the problem for every cent value up until target cent value. 在我看来,代码正在解决每个美分值直到目标美分值的问题。 Given a target value v and a set of coins C , you know that the optimal coin selection S has to be of the form union(S', c) , where c is some coin from C and S' is the optimal solution for v - value(c) (excuse my notation). 给定目标值v和一组硬币C ,您知道最优硬币选择S的形式必须为union(S', c) ,其中c是来自C某种硬币,而S'v - value(c)的最优解v - value(c) (不好意思)。 So the problem has optimal substructure . 因此,该问题具有最佳子结构 The dynamic programming approach is to solve every possible subproblem. 动态编程方法是解决所有可能的子问题。 It takes cents * size(C) steps, as opposed to something that blows up much more quickly if you just try to brute force the direct solution. 它需要几分cents * size(C)步长,而如果您只是尝试强行使用直接解决方案,则该过程会更快地崩溃。

def dpMakeChange(coinValueList,change,minCoins):
   # Solve the problem for each number of cents less than the target
   for cents in range(change+1):

      # At worst, it takes all pennies, so make that the base solution
      coinCount = cents

      # Try all coin values less than the current number of cents
      for j in [c for c in coinValueList if c <= cents]:

            # See if a solution to current number of cents minus the value
            # of the current coin, with one more coin added is the best 
            # solution so far  
            if minCoins[cents-j] + 1 < coinCount:
               coinCount = minCoins[cents-j]+1

      # Memoize the solution for the current number of cents
      minCoins[cents] = coinCount

   # By the time we're here, we've built the solution to the overall problem, 
   # so return it
   return minCoins[change]

Here is a way to think about the coin changing problem that may be useful, if you are comfortable with graph theory. 如果您熟悉图论,这是一种思考硬币更改问题的方法,该问题可能会有用。

Assume you have a graph defined in the following way: 假设您具有通过以下方式定义的图形:

  • There is one node for every unit of money (eg, pennies) from 0 up to the value you are interested in (eg, 39 cents, or whatever.) 从0到您感兴趣的价值(例如39美分或其他)之间的每一单位货币(例如几美分)都有一个节点。
  • There is one arc between any two nodes separated by exactly the value of a coin you are allowed to use (eg, a node between 34 cents and 29 cents if you are allowed to use nickels.) 任意两个节点之间都有一条弧线,该弧线之间正好是您允许使用的硬币的币值分隔开的(例如,如果您允许使用镍币,则该节点之间的弧度在34美分和29美分之间)。

Now you can think of the coin changing problem as a shortest path problem from your value of interest down to zero, because the number of coins will be exactly the same as the number of arcs in your path. 现在,您可以将硬币兑换问题视为从您的关注价值降至零的最短路径问题,因为硬币数量将与路径中的弧形数量完全相同。

The algorithm doesn't use a graph theory terminology, but it is doing basically the same thing: The outer loop is ranging over all the "cents" (or nodes, in the graph theory framework) and the inner loop is ranging over all the arcs (the values in coinValueList) from the present arc to the next arc. 该算法不使用图论术语,但基本上是在做同样的事情:外环遍及所有“分”(或图论框架中的节点),而内环遍及所有“分”。从当前弧到下一个弧的弧(coinValueList中的值)。 All together, they are looking for the shortest path from zero up to your value of interest. 总之,他们正在寻找从零到您感兴趣的价值的最短路径。 (Value down to zero, zero up to value, doesn't matter. Traditionally we search downward to zero, though.) (将值减小到零,将值减小到零是无关紧要的。不过,传统上我们会向下搜索到零。)

I only really started to understand dynamic programming when I realized many problems could be cast as graph problems. 当我意识到可以将许多问题转换为图形问题时,我才真正开始理解动态编程。 (Be careful, though-- not all of them can. Some are hypergraphs, and some are probably not even that. But it helped me a lot.) (但是请注意,并非所有人都可以。有些是超图,有些甚至还没有。但是这对我很有帮助。)

I think the fourth line is confusing because while Python can select/filter in a list comprehension (transform(x) for x in iterable if condition(x)) , it can't do the same in its standard for x in iterable: expression. 我认为第四行令人困惑,因为虽然Python可以在列表理解中选择/过滤(transform(x) for x in iterable if condition(x)) ,则(transform(x) for x in iterable if condition(x)) ,但for x in iterable:表达式中的for x in iterable:它的标准不能做到相同。

So one (cheesy imo) way people get around that is to weld the two together. 因此,人们绕过的一种(俗气的imo)方法是将两者焊接在一起。 They create a list comprehension which actually does no tranformation (thus the c for c in coinValueList ) just so they can add the if c <= cents clause on. 他们创建了一个列表c for c in coinValueList ,实际上并没有进行任何转换(因此c for c in coinValueListc for c in coinValueList )只是为了在它们上添加if c <= cents子句。 And then use that as the iterable for a standard for x in iterable: expression. 然后将其用作for x in iterable:表达式中for x in iterable:的标准for x in iterable: I suspect that's where some of your confusion is coming from. 我怀疑那是您一些困惑的来源。

An alternate way to have written that line might have been: 编写该行的另一种方法可能是:

...
for eachCoinValue in filter(lambda x: x <= cents, coinValueList):
...

Or even more clearly, with an "intention revealing variable" would be: 甚至更清楚地讲,使用“意图揭示变量”将是:

...
smallEnoughCoins = filter(lambda each: each <= cents)
for each in smallEnoughCoins:
    ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM