802. 找到最终的安全状态
题目:
思路:
【1】模拟的方式(这种方式虽然能跑,但是会超时)
【2】深度优先搜索 + 三色标记法(这种方式算是 模拟的方式 的优化版)
在深度优先搜索时,用三种颜色对节点进行标记,标记的规则如下:
白色(用 0 表示):该节点尚未被访问;
灰色(用 1 表示):该节点位于递归栈中,或者在某个环上;
黑色(用 2 表示):该节点搜索完毕,是一个安全节点。
【3】拓扑排序
将所有入度为 0 的点加入队列,然后不断取出队首元素,
将其出边相连的点的入度减一,若该点入度减一后为 0,则将该点加入队列,如此循环直至队列为空。
循环结束后,所有入度为 0 的节点均为安全的。
我们遍历入度数组,并将入度为 0 的点加入答案列表
代码展示:
【1】模拟的方式(这种方式虽然能跑,但是会超时)
class Solution { public List<Integer> eventualSafeNodes(int[][] graph) { int n = graph.length; boolean[] isRes = new boolean[n]; Set<Integer> terminal = new HashSet<Integer>(); for (int i = 0; i < n; i++){ int[] temp = graph[i]; // 如果出度为0,则是终端,应纳入终端的集合且也算是结果集中的一员 if (temp.length == 0){ terminal.add(i); isRes[i] = true; } } boolean[] flag = new boolean[n]; for (int i = 0; i < n; i++){ flag[i] = true; if (!isRes[i] && isAllPathToTerminal(graph,i,terminal,flag)){ isRes[i] = true; } flag[i] = false; } ArrayList<Integer> res = new ArrayList<>(); for (int i = 0; i < n; i++){ if (isRes[i]) res.add(i); } return res; } /** * 检测该节点开始的所有可能路径是否都通向终端节点 * @param graph 路径的图指向 * @param flag 当前节点下标 * @param terminal 终端结点集合 * @param flag 走过路径的标志 * @return */ public boolean isAllPathToTerminal(int[][] graph,int i,Set<Integer> terminal,boolean[] flag){ if (terminal.contains(i)) return true; boolean res = true; for (int data : graph[i]){ // 如果走重复的位置,则说明有环的存在(不是全路径都通向终端节点) if (flag[data] == true) return false; flag[data] = true; res &= isAllPathToTerminal(graph,data,terminal,flag); flag[data] = false; } return res; } }
【2】深度优先搜索 + 三色标记法
//时间4 ms 击败 100% //内存53.4 MB 击败 33.80% //时间复杂度:O(n+m),其中 n 是图中的点数,m 是图中的边数。 //空间复杂度:O(n)。存储节点颜色以及递归栈的开销均为 O(n)。 class Solution { public List<Integer> eventualSafeNodes(int[][] graph) { int n = graph.length; int[] color = new int[n]; List<Integer> ans = new ArrayList<Integer>(); for (int i = 0; i < n; ++i) { if (safe(graph, color, i)) { ans.add(i); } } return ans; } /** * 判断该节点是否处于安全节点 * @param graph 路径的图指向 * @param color 记录遍历节点的颜色 * 白色(用 0 表示):该节点尚未被访问; * 灰色(用 1 表示):该节点位于递归栈中,或者在某个环上; * 黑色(用 2 表示):该节点搜索完毕,是一个安全节点。 * @param x 当前节点的下标 * @return */ public boolean safe(int[][] graph, int[] color, int x) { // 如果正在处于递归状态或者遍历完的状态 if (color[x] > 0) { // 如果没有遍历完的话就说明是不处于安全点 return color[x] == 2; } color[x] = 1; for (int y : graph[x]) { // 递归状态 if (!safe(graph, color, y)) { return false; } } color[x] = 2; return true; } }
【3】拓扑排序
//时间23 ms 击败 21.66% //内存54.3 MB 击败 5.52% //时间复杂度:O(n+m),其中 n 是图中的点数,m 是图中的边数。 //空间复杂度:O(n+m)。需要 O(n+m) 的空间记录反图。 class Solution { public List<Integer> eventualSafeNodes(int[][] graph) { int n = graph.length; List<List<Integer>> rg = new ArrayList<List<Integer>>(); for (int i = 0; i < n; ++i) { rg.add(new ArrayList<Integer>()); } int[] inDeg = new int[n]; for (int x = 0; x < n; ++x) { for (int y : graph[x]) { rg.get(y).add(x); } inDeg[x] = graph[x].length; } Queue<Integer> queue = new LinkedList<Integer>(); for (int i = 0; i < n; ++i) { if (inDeg[i] == 0) { queue.offer(i); } } while (!queue.isEmpty()) { int y = queue.poll(); for (int x : rg.get(y)) { if (--inDeg[x] == 0) { queue.offer(x); } } } List<Integer> ans = new ArrayList<Integer>(); for (int i = 0; i < n; ++i) { if (inDeg[i] == 0) { ans.add(i); } } return ans; } }