题目
在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。
对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是 安全 的。
返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按 升序 排列。
该有向图有 n 个节点,按 0 到 n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。
- 输入输出样例
思路分析(反向图(链式前向星存图方式)+拓扑排序)
对拓扑排序不了解的小伙伴,可以看看拓扑排序
- 下面代码中关于链式前向星存图方式,以及代码中数组的意思不了解的,可以查看详解链式前向星存图方式
代码:
class Solution {
//定义节点的数量和边的数量,N是点的最大的数量,M定义的是最大的边数
int N=(int)1e4+10,M=4*N;
int idx=0; //idx用来对边进行编号
//其中he[i]存储的是同一起点的边的集合的头结点(即第一条边对应的索引位置,这些集合以链的形式存储)
//比如h[1]=2; 代表的是以1为起点的第一条边对应的索引位置是2,
//e:代表访问某一条边指向的节点,e[2]=6表示索引为2的这条边指向的是6
//ne[i]:由于相同的起点的边集合是以链表的形式存储,用于找到下一条边
int[] he=new int[N],e=new int[M],ne=new int[M];
int[] cnts=new int[N]; //统计每个节点的,入度
//用于存图,将图存入上述的数组中去
void add(int a,int b){
e[idx]=b;
ne[idx]=he[a];
he[a]=idx;
idx++;
}
public List<Integer> eventualSafeNodes(int[][] g) {
int n=g.length;
//首先将he数组全部用-1进行填充
Arrays.fill(he,-1);
//反向存图,并且统计反向后节点入度
for(int i=0;i<n;++i){
for(int j:g[i]){
add(j,i);
cnts[i]++;
}
}
//BFS求反向图的拓扑排序
Deque<Integer> d=new ArrayDeque<>();
//先遍历一轮,将入度为0的结点存入队列
for(int i=0;i<n;++i){
if(cnts[i]==0)d.addLast(i);
}
while(!d.isEmpty()){
int poll=d.pollFirst();
//此时要将入读为0的结点,从图中割离,并且要处理相关联的结点
for (int i=he[poll];i!=-1;i=ne[i]){
int j=e[i];
if(--cnts[j]==0)d.addLast(j);
}
}
//遍历答案:如果某个结点出现在了拓扑排序中,就说明其进入过队列,入度为0
List<Integer> ans=new ArrayList<>();
for(int i=0;i<n;++i){
if(cnts[i]==0) ans.add(i);
}
return ans;
}
}
```