Q1题目链接:https://leetcode.com/problems/course-schedule/
leetcode 207 课程设计
看到题目的第一思路:
什么时候无法修完所有课程?当存在循环依赖的时候。
其实这种场景在现实生活中也十分常见,比如我们写代码 import 包也是一个例子,必须合理设计代码目录结构,否则会出现循环依赖,编译器会报错,所以编译器实际上也使用了类似算法来判断你的代码是否能够成功编译。
代码随想录之后的想法和总结:
如果发现这幅有向图中存在环,那就说明课程之间存在循环依赖,肯定没办法全部上完;反之,如果没有环,那么肯定能上完全部课程。
1⚠️遍历图的「路径」时,需要 onPath 数组在前序位置标记节点,在后序位置撤销标记。
| 动作 | 执行时机 | 原因 |
|---|---|---|
onPath[s] = true | 前序位置 | 保证 DFS 向下递归时,路径上包含当前节点,能检测环 |
onPath[s] = false | 后序位置 | 保证离开当前节点后,路径状态恢复正常,避免误判 |
onPath 就像一条「递归路径的轨迹」:
-
前序标记:进入路径
-
后序撤销:离开路径
只有这样,整个 DFS 栈的状态始终准确反映“当前路径”。
2这段代码用到的图遍历模板类型总结:
| 模板类型 | 名称 | 特点 | 这题用途 |
|---|---|---|---|
| DFS | 深度优先遍历(递归) | 进入一个节点,递归所有邻居,再回溯 | ✅ 检测环 |
| onPath[] | 递归路径标记数组 | 标记当前递归栈中的节点 | ✅ 检测有向环 |
| hasCycle | 全局变量 | 一旦成环停止遍历 | ✅ 终止条件 |
| 邻接表建图 | buildGraph() | 高效存储稀疏图结构 | ✅ 构建课程依赖关系 |
3 visited是以整个图为维度判断是否重复遍历,onPath是以路径为维度判断是否有环,区别在于后续遍历的处理。两者不能互相替代。
visited 记录哪些节点被遍历过,而 onPath 记录当前递归堆栈中有哪些节点,它们的作用不同,所以并不重复。
(1)visited 在整个图的维度上判断一个节点是否已经被遍历过了,visited[i]==true表示该节点已经访问过了,而如果一个节点已经被访问过了,由于遍历一个点时一定会遍历完其所有的相邻节点(也即一定会遍历完以该节点开头的所有递归路径),所以当visited[i]==true时,则没有必要重复遍历该节点了,故可以减少很多的重复遍历。
(2)而 onPath数组代表的是当前递归遍历路径中已经被访问过的节点,如果 onPath[i]==true,则说明在当前路径中已经有该节点,说明成环。
时间复杂度以及空间复杂度:
| 项目 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(V + E) | 每个节点、边都只访问一次 |
| 空间复杂度 | O(V + E) | 邻接表 + 递归栈 + 辅助数组 |
Q2题目链接:https://leetcode.com/problems/course-schedule-ii/
leetcode 210 课程表2
看到题目的第一思路:
和刚才的题目类似,不仅判断课程是否可以完成,而且返回一个所有课程排序完成的课程表
在前一题的基础上 加入反转DFS得到结果集的操作即可
代码随想录之后的想法和总结:
1 什么是拓扑排序:
让你把一幅图「拉平」,而且这个「拉平」的图里面,所有箭头方向都是一致的,比如上图所有箭头都是朝右的。
很显然,如果一幅有向图中存在环,是无法进行拓扑排序的,因为肯定做不到所有箭头方向一致;反过来,如果一幅图是「有向无环图」,那么一定可以进行拓扑排序。

2 如何拓扑排序-反转DFS遍历
3为什么可以把DFS遍历反转而得到拓扑排序:
一个节点在后序时刻被“处理”,意味着它所有的依赖节点都处理完了。在有向无环图中,节点的后序时间越晚,说明它依赖的节点越多。因此,反转后序遍历顺序后,依赖的节点总是在前面 —— 满足拓扑排序的要求。
如果我们在 DFS 中,在“后序时刻”记录每个节点(例如放入列表中),得到的顺序大致是:某节点的所有后继节点都在它前面。
但是拓扑排序要求:某节点的所有前驱节点都在它前面。 这两个是「反过来的」。
👉 所以当我们 反转后序序列 时,就得到了拓扑排序。
时间复杂度以及空间复杂度:
| 项目 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(V + E) | 每个节点、每条边都只访问一次 |
| 空间复杂度 | O(V + E) | 邻接表 + DFS 栈 + 辅助数组 |
今日收获,学习时长:
BFS算法如何检测环:
1、构建邻接表,和之前一样,边的方向表示「被依赖」关系。
2、构建一个 indegree 数组记录每个节点的入度,即 indegree[i] 记录节点 i 的入度。
3、对 BFS 队列进行初始化,将入度为 0 的节点首先装入队列。
4、开始执行 BFS 循环,不断弹出队列中的节点,减少相邻节点的入度,并将入度变为 0 的节点加入队列。
5、如果最终所有节点都被遍历过(count 等于节点数),则说明不存在环,反之则说明存在环。
为什么这个算法成立?
「入度为 0 的节点」代表它没有任何依赖,可以最先被处理。
每当我们处理一个节点时,就等于“删除”它对其他节点的影响。
-
入度为 0 的节点表示「依赖已满足」;
-
每次出队即“删除”依赖;
-
当所有节点都能依次出队,说明无环,且顺序符合依赖关系。
BFS算法如何拓扑排序:记录节点的遍历顺序就是拓扑排序的结果。

7056

被折叠的 条评论
为什么被折叠?



