主题
题意
给你一个有向图,判断图中是否有环。
核心思路
如果在递归过程中,发现下一个节点在递归栈中(正在访问中),则找到了环。
如上图,我们 DFS 访问
注:说节点
「正在访问中」,是说我们正在递归处理节点 的邻居, 尚未结束。
具体思路
对于每个节点
⚠误区:不能只用两种状态表示节点「没有访问过」和「访问过」。如上图,我们先 DFS 访问
算法流程:
- 建图:把每个
看成一条有向边 ,构建一个有向图 。 - 创建长为
的颜色数组 ,所有元素值初始化成 。 - 遍历
,如果 ,则调用递归函数 。 - 执行
: - 首先标记
,表示节点 正在访问中。 - 然后遍历
的邻居 。如果 ,则找到环,返回 。如果 (没有访问过)且 返回了 ,那么 也返回 。 - 如果没有找到环,那么先标记
,表示 已经完全访问完毕,然后返回 。
- 首先标记
- 如果
返回 ,那么找到了环,返回 。 - 如果遍历完所有节点也没有找到环,返回
。
python
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
g = [[] for _ in range(numCourses)]
for a, b in prerequisites:
g[b].append(a)
colors = [0] * numCourses
# 返回 True 表示找到了环
def dfs(x: int) -> bool:
colors[x] = 1 # x 正在访问中
for y in g[x]:
# 情况一:colors[y] == 1,表示发生循环依赖,找到了环
# 情况二:colors[y] == 0,没有访问过 y,继续递归 y 获取信息
# 情况三:colors[y] == 2,重复访问 y 只会重蹈覆辙,和之前一样无法找到环,跳过
if colors[y] == 1 or colors[y] == 0 and dfs(y):
return True # 找到了环
colors[x] = 2 # x 完全访问完毕,从 x 出发无法找到环
return False # 没有找到环
for i, c in enumerate(colors):
if c == 0 and dfs(i):
return False # 有环
return True # 没有环cpp
// C++ 版待补充cpp
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> g(numCourses);
for (auto& p : prerequisites) {
g[p[1]].push_back(p[0]);
}
vector<int> colors(numCourses);
// 返回 true 表示找到了环
auto dfs = [&](this auto&& dfs, int x) -> bool {
colors[x] = 1; // x 正在访问中
for (int y : g[x]) {
// 情况一:colors[y] == 1,表示发生循环依赖,找到了环
// 情况二:colors[y] == 0,没有访问过 y,继续递归 y 获取信息
// 情况三:colors[y] == 2,重复访问 y 只会重蹈覆辙,和之前一样无法找到环,跳过
if (colors[y] == 1 || colors[y] == 0 && dfs(y)) {
return true; // 找到了环
}
}
colors[x] = 2; // x 完全访问完毕,从 x 出发无法找到环
return false; // 没有找到环
};
for (int i = 0; i < numCourses; i++) {
if (colors[i] == 0 && dfs(i)) {
return false; // 有环
}
}
return true; // 没有环
}
};复杂度分析
- 时间复杂度:
,其中 是 , 是 的长度。每个节点至多递归访问一次,每条边至多遍历一次。 - 空间复杂度:
。存储 需要 的空间。
相似题目
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府