题目来源:https://leetcode-cn.com/problems/find-eventual-safe-states/
大致题意:
给定一个有向图,对于一个节点来说,如果它可以沿着任意边都走向一个没有出边的节点,则其为安全节点。
求出所有的安全节点,按节点值大小返回。
思路
DFS+三色标记法
将节点做标记,分成三种类别:
- 未访问节点
- 已访问节点
- 安全节点
使用DFS对节点进行遍历,使用一个检查函数对节点遍历和检查。对于要遍历的节点,有:
- 若当前节点已经访问过,则其要么为安全节点,返回true;要么就不是安全节点,直接返回false。
- 标记当前节点为已访问
- DFS遍历,在遍历过程中对节点进行检查,若返回false,则当前节点为非安全节点,直接返回false。
- 若遍历结束都未返回false,则当前节点为安全节点,标记并返回true。
代码:
public List<Integer> eventualSafeNodes(int[][] graph) {
int n = graph.length;
// 存储节点状态
// 0代表未访问过,1代表已访问过,2代表为安全节点
int[] state = new int[n];
List<Integer> ans = new ArrayList<Integer>();
// 从头到尾检查
for (int i = 0; i < n; i++) {
if (safeNodeCheck(graph, state, i)) {
ans.add(i);
}
}
return ans;
}
public boolean safeNodeCheck(int[][] graph, int[] state, int node) {
// 若当前节点已经访问过,则其要么为安全节点,要么已经访问过但不是安全节点
if (state[node] > 0) {
return state[node] == 2;
}
// 标记
state[node] = 1;
// 深度优先遍历节点
for (int target : graph[node]) {
// 若目标节点不为安全节点,则当前节点不会为安全节点
if (!safeNodeCheck(graph, state, target)) {
return false;
}
}
// 若其并未标记为非安全节点,则定为安全节点
state[node] = 2;
return true;
}
拓扑排序
对于一个节点来说,如果它可以沿着任意边都走向一个没有出边的节点,则其为安全节点。
由题意可知,出度为0的节点定然为安全节点。
那么可以将图反向,则可用拓扑排序求安全节点。可知非安全节点定然是在环内,那么用拓扑排序的思想,所有在拓扑排序之后入度为0(代表其不会入环,也就是所有的边都走向安全节点)的节点定然是安全节点。
代码:
public List<Integer> eventualSafeNodes(int[][] graph) {
List<List<Integer>> reverseGraph = new ArrayList<List<Integer>>(); // 反向图
int n = graph.length;
int[] indegree = new int[n]; // 入度数组
for (int i = 0; i < n; i++) { // 初始化
reverseGraph.add(new ArrayList<Integer>());
}
// 构建反向图
for (int node = 0; node < n; node++) {
for (int target : graph[node]) {
reverseGraph.get(target).add(node);
}
// 存储入度
indegree[node] = graph[node].length;
}
Queue<Integer> queue = new LinkedList<Integer>();
// 初始时,先加入原图中出度为0的节点进队列
for (int i = 0; i < n; i++) {
if (indegree[i] == 0) {
queue.offer(i);
}
}
while (!queue.isEmpty()) {
int node = queue.poll();
for (int target : reverseGraph.add(node)) {
// 若排除该入边后,入度为0,则加入队列
if (-- indegree[target] == 0) {
queue.offer(target);
}
}
}
List<Integer> ans = new ArrayList<Integer>();
// 所有入度为0的节点即为安全节点
for (int i = 0; i < n; i++) {
if (indegree[i] == 0) {
ans.add(i);
}
}
return ans;
}