数据结构与算法之深度优先搜索与广度优先搜索

136 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情

作者: 千石
支持:点赞、收藏、评论
欢迎各位在评论区交流

前言

本文内容来自我平时学习的一些积累,如有错误,还请指正

在题目实战部分,我将代码实现和代码解释设置在了解题思路的下方,方便各位作为参考刷题

一些话

本文内容来自我平时学习的一些积累,如有错误,还请指正

在题目实战部分,我将代码实现和代码解释设置在了解题思路的下方,方便各位作为参考刷题

题目练习步骤:

  1. 给自己10分钟,读题并思考解题思路
  2. 有了思路以后开始写代码,如果在上一步骤中没有思路则停止思考并且看该题题解
  3. 在看懂题解(暂时没看懂也没关系)的思路后,背诵默写题解,直至能熟练写出来
  4. 隔一段时间,再次尝试写这道题目

前置知识

这一部分可以看看我写的这篇题解,题解中介绍了这两种算法:【后端部分】青训营题目的思维拓展:解题思路分享 - 掘金 (juejin.cn)

实战

题目一:102. 二叉树的层序遍历

解题思路

image.png

使用 BFS 的思路如下:

  1. 使用队列,将根节点入队。

  2. 只要队列不为空,循环以下操作:

    1. 将队头节点出队,并将该节点的值存入结果数组。
    2. 如果左子节点不为空,将其入队。
    3. 如果右子节点不为空,将其入队。
  3. 返回结果数组。

代码如下:

from collections import deque

class Solution:
    def levelOrder(self, root: TreeNode):
        if not root:
            return []
        result = []
        queue = deque([root])
        while queue:
            level = []
            for i in range(len(queue)):
                node = queue.popleft()
                level.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            result.append(level)
        return result

时间复杂度:O(n),因为每个节点最多被遍历一次。

使用 DFS 的思路如下:

  1. 使用递归,分别处理当前层的每个节点。
  2. 对于当前节点,先将其值存入当前层的结果数组。
  3. 如果左子节点不为空,递归处理。
  4. 如果右子节点不为空,递归处理。
  5. 返回结果数组。

代码如下:

from collections import deque

class Solution:
    def levelOrder(self, root: TreeNode):
        def dfs(node, level):
            if not node:
                return
            if len(result) < level + 1:
                result.append([])
            result[level].append(node.val)
            dfs(node.left, level + 1)
            dfs(node.right, level + 1)
        
        result = []
        dfs(root, 0)
        return result

时间复杂度:O(n),因为每个节点最多被遍历一次。

题目二:433. 最小基因变化

image.png

使用 BFS 的思路如下:

使用队列进行广度优先搜索,对于每一层的每一个字符串进行改变,生成新的字符串,如果新的字符串存在于基因库中,就将其加入队列。如果新的字符串等于 end,则返回当前层数。

from collections import deque

class Solution:
    def minMutation(self, start: str, end: str, bank: List[str]) -> int:
        def can_transform(a, b):
            count = 0
            for i in range(8):
                if a[i] != b[i]:
                    count += 1
                if count > 1:
                    return False
            return count == 1

        queue = deque([start])
        level = 0
        bank = set(bank)
        while queue:
            size = len(queue)
            level += 1
            for i in range(size):
                curr = queue.popleft()
                for j in bank:
                    if can_transform(curr, j):
                        if j == end:
                            return level
                        queue.append(j)
                        bank.remove(j)
        return -1

时间复杂度:O(N * L ^ 2),其中N为基因库中的字符串数量,L为字符串长度。

使用 DFS 的思路如下:

对于每一个可能的变换方案,我们都从当前的start状态向下搜索,看看是否能够到达最终的end状态。 如果可以,就返回当前的变换步数。如果不可以,就返回-1。

代码实现:

class Solution:
    def minMutation(self, start: str, end: str, bank: List[str]) -> int:
        def dfs(start, end, bank, path, visited):
            if start == end:
                return len(path)
            for i in range(len(bank)):
                if bank[i] not in visited and sum([start[j] != bank[i][j] for j in range(len(start))]) == 1:
                    visited.add(bank[i])
                    res = dfs(bank[i], end, bank, path + [bank[i]], visited)
                    if res != -1:
                        return res
                    visited.remove(bank[i])
            return -1
        
        return dfs(start, end, bank, [start], set([start]))

时间复杂度:O(N * L), 其中N是bank数组的大小,L是序列长度。