2025年广度优先搜索 递归(广度优先搜索递归算法)

广度优先搜索 递归(广度优先搜索递归算法)递归就是一个函数调用自身 的方法 通俗点说 就是把一个大型复杂的问题 层层转化为一个与原问题相同 的更小规模的 问题来求解 递 将问题拆解成子问题来解决 子问题再拆解成子子问题 直到被拆解的子问题无需再拆分成更细的子问题 即可以求解 归

大家好,我是讯享网,很高兴认识大家。



递归就是一个函数调用自身的方法,通俗点说,就是把一个大型复杂的问题层层转化为一个与原问题相同更小规模的问题来求解。

  1. 递:将问题拆解成子问题来解决, 子问题再拆解成子子问题,...,直到被拆解的子问题无需再拆分成更细的子问题(即可以求解)
  2. 归:最小的子问题解决了,那么它的上一层子问题也就解决了,上一层的子问题解决了,上上层子问题自然也就解决了,....,直到最开始的问题解决。

我们可以把” 递归 “比喻成 “查字典 “,当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词。可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。(摘自网络)

实质:递归的过程就是出入栈的过程,以阶乘为例:

 

讯享网


讯享网

 

  1. 第 1~4 步,都是入栈过程,调用了,又接着调用,直到;
  2. 第 5 步,因 0 是递归结束条件,故不再入栈,此时栈高度为 4,即为我们平时所说的递归深度;
  3. 第 6~9 步,做完,出栈,而做完意味着也做完,同样进行出栈,重复下去,直到所有的都出栈完毕,递归结束。
  1. 一个问题的解可以分解为若干个子问题的解;
  2. 这个问题与分解之后的子问题,除了数据规模不同,求解的思路完全相同;
  3. 存在终止条件。
  1. 需要找到递归的终止条件
  2. 找到问题与子问题间的关系,即递归的递推公式

注意:编写递归代码时,只要遇到递归,我们就把其抽象为一个递推公式,不用去想递归层层调用的关系,去分析递归的每步在做啥,这样会把我们搞混,因为递归和我们的思维方式正好相反

1.堆栈的溢出问题。

函数调用会用栈来保存临时变量,每调用一次函数,都会将临时变量压入栈中,等函数执行完返回时,出栈,如果数据规模大或者递归调用的层数深,一般就有堆栈溢出的风险

2.重复子问题。

1.汉罗塔问题(leetcode 面试题08.06)

  • 题目描述:

在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。

请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。

你需要原地修改栈。

示例1:

A中盘子的数目不大于14个。

  • 分析:

做递归时,假设A前n-1个盘子借助C都成功移到了B上,A上还剩下一个面积最大的盘子,因为要把最大的放在下面,所以将A的最后一个盘子移动到C,然后A上就没有了,再将B上的n-1个盘子借助A移动到C上,当只剩一个盘子时,就直接移动。

讯享网

2.从前序与中序遍历序列构造二叉树(leetcode 105)

  • 题目描述:【中等难度】

根据一棵树的前序遍历与中序遍历构造二叉树。

例如,给出

返回如下的二叉树:

    3
   / 
 9   20
     /   
  15    7

  • 分析:

首先,我们需要知道二叉树的前序遍历与中序遍历是如何遍历的,如下图所示(图片来源于网络),接着:

  1. 我们可以发现先序遍历的第一个节点为根节点,而在中序遍历(9 | 3 | 15 20 7)中根节点将中序遍历的结果分为左子树(9)和右子树(15 20 7)两部分。
  2. 在先序遍历的结果中,除去第一个根节点后,也分为两部分:左子树(9)和右子树(20 15 7)。
  3. 左子树和右子树又可以看成一棵树,这样就可以用递归进行求解。
  4. 当前序遍历或中序遍历只要一个元素的时候,也就是叶子节点,这是也就为递归的终止条件

 

3.对称二叉树(leetcode 101)

  • 题目描述:

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

     1
   /   
  2    2
 /     /
3  4 4  3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

    1
   /
  2   2
     
   3    3

  • 分析:

两个树互为镜像的条件(图来源于leetcode):

  1. 它们的两个根结点具有相同的值
  2. 每个树的右子树都与另一个树的左子树镜像对称

那么,根据上面的条件我们可以使用递归进行求解,参数为两个节点。

  1. 当两个节点都为空时,返回true;
  2. 当两个节点都不为空,且值相等时,判断(左->左,右->右)(左->右,右->左)
  3. 当为其他情况时,返回false。

fig2

 

  • 递归方法: 
讯享网
  • 迭代方法(见3、广度优先第二题) 

4.二叉树中的最大路径和(leetcode 124)

  • 题目描述:

给定一个非空二叉树,返回其最大路径和。

本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

示例 1:

输入: [1,2,3]

       1
      /   
     2   3

输出: 6

示例 2:

输入: [-10,9,20,null,null,15,7]

   -10
   /   
  9    20
        /   
       15   7

输出: 42

  • 分析:【参考1】【参考2】

首先,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点,而且路径不能重复

当处于一个节点的时候,可以选择不走,或者走左边,或者走右边。如果做出了选择到达了下一个节点,那么又会面临相同的选择,只不过相对上一个节点是规模更小的子树。因此可以用递归实现。

首先,终止条件是当当前节点为空时,那么返回0,表示不走了。

接着,三种选择:

  1. 走入左子树,收益:root->val + dfs(root->left)
  2. 走入右子树,收益:root->val + dfs(root->right)
  3. 停在当前子树的根节点,收益:root->val

其次,如果当前节点为负,需要特殊处理,别走它了(没想到)。

 

5.将有序数组转换为二叉搜索树(leetcode 108)

  • 题目描述:

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     /   
   -3    9
   /     /
 -10  5

  • 分析:
  • 可以选择中间数字作为二叉搜索树的根节点,这样分给左右子树的数字个数相同或只相差 1,可以使得树保持平衡。
  • 如果数组长度是奇数,则根节点的选择是唯一的;
  • 如果数组长度是偶数,则可以选择中间位置左边的数字作为根节点或者选择中间位置右边的数字作为根节点;
  • 选择不同的数字作为根节点则创建的平衡二叉搜索树也是不同的。
讯享网

深度优先遍历,通俗点说,不撞南墙,不回头。DFS本质是一种枚举,通过枚举状态空间中所有的可能性,找到一种或者若干种可行解。

解决的问题:能枚举全部状态的问题。

  1. 图的遍历中求可行路径。
  2. 排列/组合方案。
  3. 问题过于复杂,只能通过枚举+判断的问题

深度优先搜索,最重要的是如何确定状态空间,也就是说我们要搜什么,怎么搜,搜得一干二净。

回溯法,又称为“试探法”,解决问题时,每进行一步,都抱着试试看的态度,如果发现当前选择并不是最好的,或者这么走下去肯定达不到目的,立刻做回退操作,重新选择,这样走不通就回退再选择的方法就是回溯法。

“回溯”指的是“状态重置”,可以理解为“回到过去”、“恢复现场”。

树形问题

 

1.对称二叉树(leetcode 101)

解析:广度优先中的另解

2.N叉树的最大深度(leetcode 559)

广度优先:

 

1.验证二叉搜索树(leetcode 98)

  • 题目描述:

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

  • 分析:

二叉搜索树有如下性质:

  1. 节点的左子树只包含小于当前节点的数。
  2. 节点的右子树只包含大于当前节点的数。
  3. 所有左子树和右子树自身必须也是二叉搜索树。

从性质3中我们可以发现这道题的方法——递归,首先验证父节点是否满足性质1,2,然后递归调用再验证左右子树是否也是二叉搜索树。

注意(很重要):这里有一个区间限制,左子树上的节点都比根节点的值小,同理,右子树上的节点都比根节点的大,下面这个就不是二叉搜索树。

讯享网

2.另一个数的子树(leetcode 572)

  • 题目描述:

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

  • 分析:
  1. 首先,我们需要在主树中找到与子树相同的节点。
  2. 然后,进行深度遍历。
  3. 接着,判断两个节点是否为空,如果是,则为true,否则,判断是否其中一个节点为空,如果是,则为false,否则,判断节点的值是否相等,如果是,按照上述条件判断左节点和右节点。
 

3. 二叉树的最近公共祖先(leetcode 236)

  • 题目描述:(中等难度)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 分析1:

注:来源于leetcode 官方题解

讯享网
  •  分析2:
  1. 如果当前结点 root 等于NULL,则直接返回NULL
  2. 如果 root 等于 p或者 q ,那返回 p 或者 q
  3. 然后递归左右子树,递归结果,用 left 和 right表示
  4. 如果 left 和 right都非空,说明左右子树一边一个,因此 root是他们的最近公共祖先
  5. 如果 left为空,返回right,否则left;
 

4.电话号码的字母组合(leetcode 17)

  • 题目描述:

给定一个仅包含数字  的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例:

讯享网
  • 分析:

这道题是字母组合的问题,我们可以画出如下的树形结构得到我们所需要的所有可能。我们采用回溯法解决。

 

5.全排列(leetcode 46)

  •  分析:

对[1,2,3]进行全排列={取出其中一个数字}+对剩余的数字进行全排列,这也就是递归操作。

  • 实现: 
讯享网

结果: [[1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3],[2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3],[3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3]]

 

 结果:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

广度优先搜索算法(英语:Breadth-First Search,缩写为BFS),又译作宽度优先搜索,或横向优先搜索,通俗点说一层一层的剥开你的心

  • 原理:

某个结点出发,BFS 首先遍历到距离为 1 的结点,然后是遍历距离为 2、3、4…… 的结点。因此,BFS 可以用来求最短路径问题。BFS 先搜索到的结点,一定是距离最近的结点。

BFS 与最短路径

  • 什么时候用广度优先遍历(BFS)
  1. 层级遍历
  2. 由点到面
  3. 拓扑排序
  4. 最短路径:简单图求最短路径,即图中每条边为1,且没有方向。
  • 实现模板
讯享网

对于上述模板,无法区分每一层是否遍历完成。因为在遍历的时候,第1层的节点出队,如果条件满足,第2层的节点紧接着入队,这使得队列中第 1 层和第 2 层的结点会紧挨在一起,无法区分,也就无法知道每个结点的距离 depth 了。

因此,我们需要稍微修改一下代码,在每一层遍历开始前,记录当前队列中的结点数量 num ,然后一口气处理完这一层的num 个结点 。这样,我们就知道这num个结点位于同一层了。然后遍历下一层的时候,把变量 depth 加一。代码框架是这样的:

 

1.从上到下打印二叉树 II(剑指offer 32)

  • 题目描述:

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

例如:

给定二叉树: [3,9,20,null,null,15,7],

    3
   / 
  9  20
     /   
   15    7
返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]

提示:

节点总数 <= 1000

  • 分析:

二叉树的层级遍历

讯享网

 2.二叉树的层次遍历(leetcode 102)

给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。

 

返回其层次遍历结果:

讯享网
  • 实现
  1.  判断根节点是否为NULL,如果是,直接返回空。
  2. 将根节点插入到队列中,遍历整个队列。
  3. 队列的顶部元素出队,存储其值。
  4. 判断左右节点是否为空,如果不为空,则将其入队。
  5. 重复步骤3.

注意:需要记得队列中现目前的元素数量

 

 
  • 另外的解法: 

本题使用 DFS 同样能做。由于题目要求每一层的节点都是从左到右遍历,因此递归时也要先递归左子树、再递归右子树。

DFS 做本题的主要问题是: DFS 不是按照层次遍历的。为了让递归的过程中同一层的节点放到同一个列表中,在递归时要记录每个节点的深度 level。递归到新节点要把该节点放入 level 对应列表的末尾。

当遍历到一个新的深度 level,而最终结果 res 中还没有创建 level 对应的列表时,应该在 res 中新建一个列表用来保存该 level 的所有节点。

参考:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/solution/tao-mo-ban-bfs-he-dfs-du-ke-yi-jie-jue-by-fuxuemin/

讯享网

3.二叉树的层次遍历II(leetcode 107)

  • 题目描述:

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

    3
   /   
  9   20
      /   
   15    7
返回其自底向上的层次遍历为:

[
  [15,7],
  [9,20],
  [3]
]

  • 分析:
 

4.二叉树的最小深度(leetcode 111)

  • 题目描述:

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7],

    3
   / 
  9  20
    /   
   15   7
返回它的最小深度  2.

  • 分析:
讯享网

5.N叉树的最大深度(leetcode 559)

  • 题目描述:

给定一个 N 叉树,找到其最大深度。

最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

例如,给定一个 3叉树 :

我们应返回其最大深度,3。

说明:

  • 分析:

广度优先,一层一层的遍历,也就是这棵树有多少层。

 

 

 

 

 

 

 

 

 

2.对称二叉树(leetcode 101)

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   /
  2   2
 / /
3  4 4  3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

    1
   /
  2   2
     
   3    3

  •  分析:
  • 对整棵二叉树进行层次遍历,节点分为左子树和右子树进行存储,
  • 首先,入队。
  • 左右队列队首出队,判断是不是都为空节点。
  • 如果都是空节点,则跳过;
  • 如果不是,则判断是不是都不为空且值相等,如果是,则将左孩子和右孩子入队。
  • 重复上述步骤
  • 实现:

bfs:

讯享网

dfs:

 

 3.二叉树的右视图(leetcode 199) 

  • 日期:2020/4/22
  • 题目描述:

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例:

输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:

   1            <—
 /  
2     3         <—
     
  5     4       <—

  • 分析:

其实这道题是二叉树层次遍历的变种,我们层次遍历的时候是从左往右进行存储节点,但是这里我们需要从右往左进行存储节点,其次,我们每层只取第一个节点放到我们的最终结果中。

讯享网

4.腐烂的橘子(leetcode 994)

在给定的网格中,每个单元格可以有以下三个值之一:

  • 值  代表空单元格;
  • 值  代表新鲜橘子;
  • 值  代表腐烂的橘子。

每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。

返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 。

示例 1:

 

示例 2:

讯享网

示例 3:

 

提示:

  1.  仅为 、 或 
  •  实现
  1. 找出初始腐烂橘子的位置,插入到队列中。
  2. 判断队列是否为空,如果不为空,队列的顶部元素出队,对上、下、左、右四个方向的橘子进行腐烂操作,腐烂的橘子置为2。注意:需要在边界处判断是否越界。
  3. 新的腐烂橘子插入到队列中,重复2。
  4. 然后再次遍历整个区域,判断是否把所有的橘子都腐烂了。
讯享网

5.水壶问题(leetcode 365)

  • 日期:2020/3/21

有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?

如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。

你允许:

  • 装满任意一个水壶
  • 清空任意一个水壶
  • 从一个水壶向另外一个水壶倒水,直到装满或者倒空

示例 1: (From the famous “Die Hard” example)

 

示例 2:

讯享网
  •  补充学习知识:std::unordered_set自定义函数

参考:STL: unordered_map 自定义键值类型的使用(C++)

  • 分析:(来源于图解BFS C++ 借助 unordered_set / queue 实现)

以 x = 1, y = 3, z = 2 为例,一共有八种状态:(0,0), (1,0),(0,1), (1,1),(0,2), (1,2),(0,3), (1,3)。可以以这八种状态构建如下的图:

倒水问题构图.png

  • 节点表示两个水壶的状态
  • 边表示操作方法:分别为倒满A/B,倒空A/B,A倒入B,B倒入A 六种方法。
  • 这是一个有向图,因为有些状态并不能护互为转移。比如 (1,1) 和 (1,0)。

广度优先搜索(BFS):该过程总是从一个或若干个起始点开始,沿着边像水波一样逐层向外遍历,直到所有的点已被访问或者到达目标状态

  • 实现

1.初始时,队列和set均为空。将起始点放入队列及set。

2.如果队列为则 bfs 结束。

3.弹出队首元素并访问其周围元素,设为 p。

4.如果p为目标状态则 bfs 结束。

5. p 执行6种操作,将不在set中的元素放入队列及set。跳转第 2 步。

 

6.地图分析(leetcode 1162)

你现在手里有一份大小为 N x N 的『地图』(网格) grid,上面的每个『区域』(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,你知道距离陆地区域最远的海洋区域是是哪一个吗?请返回该海洋区域到离它最近的陆地区域的距离。

我们这里说的距离是『曼哈顿距离』( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是 |x0 - x1| + |y0 - y1| 。

如果我们的地图上只有陆地或者海洋,请返回 -1。

 

示例 1:

输入:[[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释: 
海洋区域 (1, 1) 和所有陆地区域之间的距离都达到最大,最大距离为 2。

输入:[[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释: 
海洋区域 (2, 2) 和所有陆地区域之间的距离都达到最大,最大距离为 4。
 

提示:

参考:

https://leetcode-cn.com/problems/as-far-from-land-as-possible/solution/li-qing-si-lu-wei-shi-yao-yong-bfs-ru-he-xie-bfs-d/

7.机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1:

输入:m = 2, n = 3, k = 1
输出:3
示例 1:

输入:m = 3, n = 1, k = 0
输出:1
提示:

【记住】错误: 哎,本来思路很清晰,没一会就将代码写完了,但是由于自己在遍历方向数组的时候,命名命成了范围K,导致变量命名重复,浪费了时间。

讯享网

8.01矩阵(leetcode 542)

  • 题目描述:

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

0 0 0
0 1 0
0 0 0
输出:

0 0 0
0 1 0
0 0 0


示例 2:
输入:

0  0  0
0  1  0
1  1  1
输出:

0 0 0
0 1 0
1 2 1

给定矩阵的元素个数不超过 10000。
给定矩阵中至少有一个元素是 0。
矩阵中的元素只在四个方向上相邻: 上、下、左、右。

9.岛屿数量(leetcode 200)

  • 日期:2020/4/20
  • 题目描述:

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:
11110
11010
11000
00000
输出: 1

示例 2:

输入:
11000
11000
00100
00011
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。

  • 分析:

首先,“1”表示陆地,“0“表示海洋,每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成,我们可以通过深度优先搜索的方法通过起点寻找相邻的陆地(连通区域)构成岛屿,直到找到陆地的边界才返回,对于寻找过程中找过的陆地赋予新值:”2“。

  •  代码实现:
  • 深度优先搜索
 
  • 广度优先搜索
讯享网

10.课程表II(leetcode 210)

  • 题目描述:

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

示例 1:

输入: 2, [[1,0]] 
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
     因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
拓扑排序也可以通过 BFS 完成。

  • 分析:(参考)

我们用 有向图 描述这种 依赖关系 (做事的先后关系):例如:n = 6, 先决条件表:[ [3, 0], [3, 1], [4, 1], [4, 2], [5, 3], [5, 4] ]

不能“跳步”,选你【能上的课】

  1. 当下只能选【入度为 0 的课】,因为它不依赖别的课。假设先选了 0
  2. 这导致 依赖 0 的课的入度减小 —— 3 的入度由 2 变 1
  3. 接着选 1,导致课 3 的入度变 0,课 4 的入度由 2 变 1
  4. 接着选 2,导致课 4 的入度变 0,当前 3 和 4 入度为 0
  5. 继续选【入度为 0 的课】……
  6. ……
  7. 直到选不到【入度为 0 的课】

这形似【树的BFS】

  1. 起初让【入度为 0 的课】入列
  2. 然后 逐个出列,课出列 = 课被选,减小相关课的入度
  3. 判定是否有 入度转 0 的课,继续入列、出列……
  4. 直到没有【入度为 0 的课】可入列……

BFS 前的准备工作

  1. 我们关心【每门课对应的入度】—— 它要被减,它要被监控
  2. 我们关心【课之间的依赖关系】—— 选这门课会减小哪些课的入度
  3. 因此我们需要 合适的数据结构,去存储这些关系

构建入度数组

  1. 每一门课都有一个动态变化的入度
  2. 课的编号是 0 到 n - 1,让它作为索引,选用 一维数组 存放 入度
  3. 遍历 先决条件表 (二维数组),计算每门课的初始入度

构建哈希表

  1. 我们选用 哈希表 即【相邻衔接表】来记录 依赖关系
  2. map 存什么键值对:
  3. 键: 课的编号
  4. 值: 依赖它的后续课程 ( list 数组)
  5. 比如:修完 2 才能修 4 和 5
  6. 2: [4, 5]
  7. 也可以用 邻接矩阵,但二维矩阵它有点大

BFS 思路

  1. queue 队列中始终是【入度为 0 的课】在里面流动
  2. 选择一门课,就让它 出列,同时 查看哈希表,看它 对应哪些后续课
  3. 将这些后续课的 入度 - 1,如果有 减至 0 的,就将它 推入 queue
  4. 不再有新的入度 0 的课入列 时,此时 queue 为空,退出循环
 

 

小讯
上一篇 2025-04-20 10:36
下一篇 2025-05-07 15:40

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/176729.html