在算法题库的中高频题目中,图论中的环检测和树形结构的设计是两个绕不开的坎。
本文通过两道经典题目——207. 课程表 和 208. 实现 Trie (前缀树),来深入理解 DFS(深度优先搜索)在不同场景下的妙用,以及如何亲手设计一个高效的数据结构。
一、课程表 (Course Schedule)
1. 题目核心:图的有向环检测
题目给定了一组课程的依赖关系(比如想学 A 必须先学 B),问我们能不能修完所有课。这本质上是在问:这个有向图中是否存在环? 如果存在环(例如 A->B->C->A),就会形成'死锁',导致无法完成。
2. 解题法宝:三色标记法 (DFS)
为了防止 DFS 陷入死循环,并准确判断'当前路径'是否有环,我们需要三个状态,而不仅仅是访问过/没访问过。
- 0 (白色/White):未访问。完全没探索过的未知区域。
- 1 (灰色/Gray):正在访问中。当前侦探正在走这条路,还没走到底。如果 DFS 过程中撞见了 1,说明咬到了自己的尾巴,发现环!
- 2 (黑色/Black):已完成。这条路已经走到底并退出来了,确认是安全的(无环)。
3. 代码实现 (C++)
在使用 DFS 遍历邻接表时,最容易犯的错误就是混淆循环下标和邻居节点的值,代码中已重点标注。
class Solution {
vector<vector<int>> g; // 邻接表:存图
vector<int> sign; // 三色标记数组
// 思路:基于三色标记法的 DFS 思路
bool dfs(int i) {
sign[i] = 1; // 标记为 1 (灰色/正在访问),表示'正在这条路上'
// 遍历当前节点 i 的所有邻居
for (int j = 0; j < g[i].size(); ++j) {
int neighbor = g[i][j]; // 【关键】取出真正的邻居节点,千万别用成 j
// 情况一:撞到了'正在访问'的节点 (sign=1)
// 说明我们绕了一圈又回到了当前路径上的某个点!这就是环!
if (sign[neighbor] == 1) {
;
}
(sign[neighbor] == ) {
((neighbor)) {
;
}
}
}
sign[i] = ;
;
}
:
{
g.(numCourses, <>());
sign.(numCourses, );
( i = ; i < prerequisites.(); ++i) {
prev = prerequisites[i][];
curr = prerequisites[i][];
g[curr].(prev);
}
( i = ; i < numCourses; ++i) {
(sign[i] == ) {
flag = (i);
(flag == ) {
;
}
}
}
;
}
};


