小知识:BFS求解查找零钱

149 阅读2分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

题目是leetcode 322,题意时:有不同面额的硬币,问是否能够以这些硬币凑成给定的金额。要求在有解的情况下,找出所需要的最少金币数量 。如果无法凑出给定的金额,则返回-1。

我想从图论的角度来做这道题,对于对于任意的金额,把它看作图的一个顶点。对于这样的每一个顶点,它可以到达和它距离位于coins包含的值的顶点的位置。同理,它可以从这样的顶点来。举个例子,像下面的图这样,2,3可以到4这个顶点,同样的,4可以到5,6。

image.png 顶点之间的邻接关系是由这个coins硬币面额数组决定的。

由此,这个问题就转化成了,图论中的路径问题。求从0开始到给定总金额对应顶点的最短路径。

我们可以使用BFS来求解这个问题,BFS天然就是用来求解这种最短路径问题的,在这个题目里,实际上可以看作每条边都是等价的,步长为一。

具体过程就是使用一个队列,每次去出队列中的元素的后继,直到没有后继了,或者找到了

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        que = [(0, 0)]
        visited = [False]*(amount+1)
        while que:
            successors = [] 
            for value, count in que:
                for i in coins:
                    if value == amount:
                        return count
                    if i + value <= amount and not visited[i+value]: # 1
                        visited[value+i] = True # 2
                        successors.append((value+i, count+1))
            que = successors
        return -1

为了得到每个元素的当前步数(或者说当前使用的硬币数量),我们使用元组来表示每一个顶点。

代码中使用了visited进行已访问的标志,因为图不同于树,显然是容易出现一个顶点被访问两次的情况。比如上面画的图,4既可以由2也可以由3得到。除此之外,当接近顶点时,很容易发生的一个情况时,当前顶点的这个后继比总金额大,但是我们定义visited数组只有amount+1(0到amount才是需要考虑的区间)这么大,为了防止数组越界,我们使用下标之前判断了一下后继节点的位置(当下标大于总金额的时候没有必要访问了)。

另外为了更高的效率,将visited标为已访问放置在了加入节点的时候。比起从队列中拿出时再标记,这样可以更多的避免重复的节点被放置到队列中。

最后提交下代码

image.png