题目描述
在有向图中,我们从某个结点和每个转向处开始,沿着图的有向边走。如果我们到达的结点是终点(即它没有连出的有向边),我们停止。
现在,如果我们最后能走到终点,那么我们的起始结点是 最终安全的。更具体地说,存在一个自然数 K
,无论选择从哪里开始行走,我们必能在走了不到 K
步时停止在一个终点。
哪些结点最终是安全的?结果返回一个有序的数组。
该有向图有 N
个结点,标签为 0, 1, ..., N-1
,其中 N
是 graph
的结点数。图以以下的形式给出:graph[i]
是结点 j
的一个列表,满足 (i, j)
是图的一条有向边。
样例
输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]]
输出:[2,4,5,6]
这里是上图的示意图。
提示
graph
结点数不超过10000
。- 图的边数不会超过
32000
。 - 每个
graph[i]
被排序为不同的整数列表,范围在区间[0, graph.length - 1]
。
算法
(拓扑排序) $O(n + m)$
- 将整个图的所有边反向,可以发现符合要求的结点是那些从入度为 0 的点不经过环能达到的点。
- 这也就是拓扑序的定义,所以我们可以通过拓扑排序求出可到达的点。
时间复杂度
- 每个点和边最多被遍历一次,故时间复杂度为 $O(n + m)$。
空间复杂度
- 需要额外的数组存储反向图,需要队列进行拓扑排序,故空间复杂度为 $O(n + m)$。
C++ 代码
class Solution {
public:
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
int n = graph.size();
vector<vector<int>> graph_rev(n);
vector<int> indeg(n, 0);
for (int i = 0; i < n; i++) {
for (auto &j : graph[i])
graph_rev[j].push_back(i);
indeg[i] = graph[i].size();
}
queue<int> q;
for (int i = 0; i < n; i++)
if (indeg[i] == 0) {
q.push(i);
}
while (!q.empty()) {
int sta = q.front();
q.pop();
for (auto &v : graph_rev[sta]) {
indeg[v]--;
if (indeg[v] == 0) {
q.push(v);
}
}
}
vector<int> ans;
for (int i = 0; i < n; i++)
if (indeg[i] == 0)
ans.push_back(i);
return ans;
}
};
为什么不反向存图,直接拓扑排序不行?
直接拓扑排序对于,
[[1], [0, 2], []]
这样的图,结点2
就会找不到。因为这个图是一个环,然后环上的某个结点有一个出边,出边上的点如果用正向图拓扑排序就会找不到。