一、题目
在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。
对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是 安全 的。
返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按 升序 排列。
该有向图有 n 个节点,按 0 到 n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。
点击查看原题
难度级别: 中等
二、思路
1)三色标记法
利用三色标记法可以记录下状态,凡是遍历过的都会被标记上颜色,令0为未访问过,1为不可达或在dfs栈中,2为安全节点。凡只能到达2节点或出度为0的,颜色都为2,只要能到达颜色1,都染色为1.
2)拓补排序
能够只达到安全节点的节点,其出度一定都在安全节点上,所以我们反向建表,以安全节点为起点,进行拓补排序,遍历到节点会将其出度减1,整体都遍历结束后,出度不为0的节点为非安全节点。
三、代码
1)三色标记法
class Solution {public List<Integer> eventualSafeNodes(int[][] graph) {int[] color = new int[graph.length];List<Integer> ans = new ArrayList();for (int i = 0; i < graph.length; i++) {if (isSafe(graph, color, i)) {ans.add(i);}}return ans;}public boolean isSafe(int[][] graph, int[] color, int node) {if (color[node] > 0) {return color[node] == 2;}color[node] = 1;for (int next : graph[node]) {if (!isSafe(graph, color, next)) {return false;}}color[node] = 2;return true;}}
2)拓补排序
class Solution {public List<Integer> eventualSafeNodes(int[][] graph) {List<List<Integer>> reverseG = new ArrayList();int[] inDegree = new int[graph.length]; // 反向建图后,原图的出度为反向图入度// 逆向图并初始化for (int i = 0; i < graph.length; i++) {reverseG.add(new ArrayList());}for (int i = 0; i < graph.length; i++) {for (int j : graph[i]) {reverseG.get(j).add(i);}inDegree[i] = graph[i].length;}Queue<Integer> queue = new LinkedList();// 将原图为安全节点的节点作为起始节点for (int i = 0; i < graph.length; i++) {if (graph[i] == null || graph[i].length == 0) {queue.offer(i);}}// 根据起始节点顺序向下遍历,能够安全到达起始节点的入度会降为0,能达到其他非安全节点的就会大于0while (!queue.isEmpty()) {int node = queue.poll();for (int next : reverseG.get(node)) {if (--inDegree[next] == 0) {queue.offer(next);}}}List<Integer> ans = new ArrayList();for (int i = 0; i < inDegree.length; i++) { // 将入度为0的节点汇总成答案if (inDegree[i] == 0) {ans.add(i);}}return ans;}}
时间复杂度为O(n+m),空间复杂度为O(n+m)
