Description
在有向圖中,從某節點出發,若無論走哪條路徑最終都會到達終端節點(出度為 0),則該節點是安全節點。回傳所有安全節點(升序排列)。
Example:
Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]] Output: [2,4,5,6]
Intuition#
安全節點 = 不在任何環上的節點。用 DFS 標記環上節點,或用反向拓撲排序。
- 終端節點(出度 0)一定安全
- 所有路徑都指向安全節點的節點也是安全的
- 在環上的節點一定不安全
Approaches#
1: DFS 三色標記#
- 概念: 用三種狀態(未訪問/處理中/安全)做 DFS。若 DFS 遇到「處理中」節點代表有環,該路徑上的節點不安全。
- 時間複雜度:
O(V + E) - 空間複雜度:
O(V)
class Solution {
fun eventualSafeNodes(graph: Array<IntArray>): List<Int> {
val n = graph.size
// 0: 未訪問, 1: 處理中, 2: 安全
val state = IntArray(n)
fun dfs(node: Int): Boolean {
if (state[node] != 0) return state[node] == 2
state[node] = 1
for (next in graph[node]) {
if (!dfs(next)) return false
}
state[node] = 2
return true
}
return (0 until n).filter { dfs(it) }
}
}⭐2: 反向圖拓撲排序#
- 概念: 建立反向圖,從終端節點(原圖出度 0)開始拓撲排序,能被處理到的節點都是安全的
- 時間複雜度:
O(V + E) - 空間複雜度:
O(V + E)
class Solution {
fun eventualSafeNodes(graph: Array<IntArray>): List<Int> {
val n = graph.size
val outDeg = IntArray(n)
val reverseGraph = Array(n) { mutableListOf<Int>() }
for (u in 0 until n) {
for (v in graph[u]) {
reverseGraph[v].add(u)
}
outDeg[u] = graph[u].size
}
val queue = ArrayDeque<Int>()
for (i in 0 until n) {
if (outDeg[i] == 0) queue.add(i)
}
val safe = BooleanArray(n)
while (queue.isNotEmpty()) {
val node = queue.removeFirst()
safe[node] = true
for (prev in reverseGraph[node]) {
outDeg[prev]--
if (outDeg[prev] == 0) queue.add(prev)
}
}
return (0 until n).filter { safe[it] }
}
}🔑 Takeaways#
- Pattern: 環偵測 / 拓撲排序 – 安全節點等同於不在環上的節點
- 關鍵技巧: DFS 三色標記法簡潔高效;反向圖拓撲排序利用出度為 0 的節點作為起點