【LeetCode】111 and 752(广度优先搜索,BFS)

广度优先搜索

核心思想:把⼀些问题抽象成图,从⼀个点开始,向四周开始扩散。⼀般来说,我们写 BFS 算法都是⽤「队列」这种数据结构,每次将⼀个节点周围的所有节点加⼊队列。
常⻅场景:问题的本质就是让你在⼀幅「图」中找到从起点start 到终点 target 的最近距离。
BFS 相对 DFS 的最主要的区别是:BFS 找到的路径⼀定是最短的,但代价就是空间复杂度可能⽐ DFS ⼤很多。
BFS的代码框架如下:
在这里插入图片描述

111. 二叉树最小深度

在这里插入图片描述
在这里插入图片描述
解法:BFS
⾸先明确⼀下起点 start 和终点 target 是什么,怎么判断到达了终点?
显然起点就是 root 根节点,终点就是最靠近根节点的那个「叶⼦节点」嘛,叶⼦节点就是两个⼦节点都是None 的节点。按照上述框架撰写求解代码如下:

from queue import Queue
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        que = Queue()
        que.put(root)
        # root 本身就是⼀层,depth 初始化为 1
        step = 1
        while not que.empty():
            size = que.qsize()
            # 将当前队列中的所有节点向四周扩散
            for i in range(size):
                node = que.get()
                # 判断是否到达终点
                if not node.left and not node.right:
                    return step
                # 将 cur 的相邻节点加⼊队列
                if node.left:
                    que.put(node.left)
                if node.right:
                    que.put(node.right)
            # 层数+1
            step += 1
        return step

752. 打开转盘锁

在这里插入图片描述
在这里插入图片描述
解法:BFS
难点就在于,不能出现 deadends,应该如何计算出最少的转动次数呢?
第⼀步,我们不管所有的限制条件,不管 deadends 和 target 的限制,就思考⼀个问题:如果让你设计⼀个算法,穷举所有可能的密码组合,你怎么做?
直接的想法就是穷举,再简单⼀点,如果你只转⼀下锁,有⼏种可能?总共有 4 个位置,每个位置可以向上转,也可以向下转,也就是有 8 种可能。
⽐如说从 “0000” 开始,转⼀次,可以穷举出 “1000”, “9000”, “0100”, “0900”… 共 8 种密码。然后,再以这 8 种密码作为基础,对每个密码再转⼀下,穷举出所有可能…
这个过程可以抽象成⼀幅图,每个节点有 8 个相邻的节点,⼜让你求最短距离,这就是典型的BFS问题。可以使用上述框架求解。同时我们需要考虑以下情况的处理,对框架代码稍加修改:
1、会⾛回头路。⽐如说我们从 “0000” 拨到 “1000”,但是等从队列拿出 “1000” 时,还会拨出⼀个"0000",这样的话会产⽣死循环。
2、没有终⽌条件,按照题⽬要求,我们找到 target 就应该结束并返回拨动的次数。
3、没有对 deadends 的处理,按道理这些「死亡密码」是不能出现的,也就是说你遇到这些密码的时候需要跳过。

from queue import Queue
class Solution:
    def openLock(self, deadends: List[str], target: str) -> int:
        passwords = '0000'
        que = Queue()
        que.put(passwords)
        # 记录已经穷举过的密码,防⽌⾛回头路
        visited = set(deadends)
        # 从起点开始启动⼴度优先搜索
        step = 0
        # 记录需要跳过的死亡密码
        deads = set(deadends)
        while not que.empty():
            size = que.qsize()
            # 将当前队列中的所有节点向周围扩散
            for _ in range(size):
                node = que.get()
                # 判断是否到达终点
                if node in deads:
                    continue
                if node == target:
                    return step
                # 将⼀个节点的未遍历相邻节点加⼊队列
                for i in range(4):
                    up = self.addOne(node, i)
                    if up not in visited:
                        que.put(up)
                        visited.add(up)
                    down = self.minusOne(node, i)
                    if down not in visited:
                        que.put(down)
                        visited.add(down)
            # 在这⾥增加步数
            step += 1
        # 如果穷举完都没找到⽬标密码,那就是找不到了
        return -1
    # 将 passwords[position] 向上拨动⼀次
    def addOne(self, passwords, position):
        passwords = list(map(lambda x: int(x), list(passwords)))
        if passwords[position] == 9:
            passwords[position] = 0
        else:
            passwords[position] += 1
        return "".join(map(lambda x: str(x), passwords))
    # 将 passwords[position] 向下拨动⼀次
    def minusOne(self, passwords, position):
        passwords = list(map(lambda x: int(x), list(passwords)))
        if passwords[position] == 0:
            passwords[position] = 9
        else:
            passwords[position] -= 1
        return "".join(map(lambda x: str(x), passwords))

总结

1、为什么 BFS 可以找到最短距离,DFS 不⾏吗?
DFS也能找到最短距离,但是根据BFS 的逻辑,depth 每增加⼀次,队列中的所有节点都向前迈⼀步,这保证了第⼀次到达终点的时候,⾛的步数是最少的。
形象点说,DFS 是线,BFS 是⾯;DFS 是单打独⽃,BFS 是集体⾏动。

2、既然 BFS 那么好,为啥 DFS 还要存在?
BFS 可以找到最短距离,但是空间复杂度⾼,⽽ DFS 的空间复杂度较低。
以处理⼆叉树问题为例⼦,假设给你的这个⼆叉树是满⼆叉树,节点数为 N,对于 DFS 算法来说,空间复杂度⽆⾮就是递归堆栈,最坏情况下顶多就是树的⾼度,也就是 O(logN)。
但是你想想 BFS 算法,队列中每次都会储存着⼆叉树⼀层的节点,这样的话最坏情况下空间复杂度应该是树的最底层节点的数量,也就是 N/2,⽤ Big O 表示的话也就是 O(N)。
由此观之,BFS 还是有代价的,⼀般来说在找最短路径的时候使⽤ BFS,其他时候还是 DFS 使⽤得多⼀些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值