文章目录
802.找到最终的安全状态
题目描述
在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。
对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是安全的。
返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按升序排列。
该有向图中有n个节点,按0到n-1编号,其中n是graph的节点数。图以下述形式给出:graph[i]是编号j节点的一个列表,满足(i, j)是图的一条有向边。
示例1
输入:
graph = [[1, 2], [2, 3], [5], [0], [5], [], []]
输出:
[2, 4, 5, 6]
解释:
示意图如上。graph = [[1, 2], [2, 3], [5], [0], [5], [], []]中的graph[0]=[1, 2]表示的是节点0有两条有向边分别指向节点1和节点2,其他同理。
示例2
输入:
graph = [[1, 2, 3, 4], [1, 2], [3, 4], [0, 4], []]
输出:
[4]
提示:
- n == graph.length
- 1 <= n <= 104
- 0 <= graph[i].length <= n
- graph[i]按严格递增顺序排列
- 图中可能包含自环
- 图中边的数目在范围[1, 4*104]内
思路:DFS、拓扑排序
DFS+普通标记:
根据题意,若起始节点位于一个环内,或者能到达一个环,则该节点是不安全的。否则该节点是安全的。
可以使用DFS找环,并在DFS过程中,用不同的值标记节点,标记规则如下:
- -1:该节点未访问
- 0:该节点是安全的
- 1:该节点是走过的不确定安不安全
- 2:该节点是不安全的
初始化每个节点都标记为-1,DFS遍历,当首次访问一个节点时标记为1,当遇到环标记为2,提前结束循环,当一个节点所有出的边指向的结点都是安全的,则它也为安全的,标记它为0,最后找到所有标记为0的节点,并按升序排列返回。
当然,这个标记也可以简化为只有True和False的标记。
python代码
class Solution:
def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
# 方法一:DFS+标记
n = len(graph)
# 每个点可能的状态:-1表示点是未走过的,0表示点是安全的,
# 1表示点是访问过的但不确定安全与否,2表示点时不安全的
# 定义list表示每个结点当前状态
states = [-1]*n
# dfs
def dfs(node):
# 节点还未被访问
if states[node] == -1:
# 标记状态1
states[node] = 1
for nextnode in graph[node]:
states[node] += dfs(nextnode)
# 不安全的节点可以提前结束循环
if states[node] > 1:
break
# 出的所有点为安全的,它才是安全的
states[node] = 0 if states[node] == 1 else 2
return states[node]
return [i for i in range(n) if not dfs(i)]
DFS+boolean标记
该思路是对前一种思路的优化,减少了标记种类
python代码
class Solution:
def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
# 方法二DFS+boolean标记
n = len(graph)
# 安全为True,不安全为False
states = [None] * n
def dfs(node):
if states[node] is None:
states[node] = False
if all(dfs(nextnode) for nextnode in graph[node]):
states[node] = True
return states[node]
return [i for i in range(n) if dfs(i)]
拓扑排序
依据题意,如果一个节点没有边,则该节点是安全的,如果一个节点出边连接的所有节点都是安全的,则该节点是安全的。
根据这个性质,可以对图进行拓扑排序。
具体操作如下:搜索的时候标记节点当前状态,如果有出口,标记为1,如果它的出口全部为安全的节点,它们的和必然为0,则认为这个节点也是安全的,否则是不安全的。
python代码
class Solution:
def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
# 方法三拓扑排序
n = len(graph)
out = [0]*n
edges = defaultdict(list)
for i, nodes in enumerate(graph):
for node in nodes:
edges[node].append(i)
# 统计所有点的出度
out[i] += 1
q = deque([])
for i in range(n):
if not out[i]:
# 出度为0的点加入队列
q.append(i)
while q:
node = q.popleft()
for front in edges[node]:
# 去掉front->node这条边
out[front] -= 1
if not out[front]:
# 如果去掉该条边front出度为0,则加入队列
q.append(front)
return [i for i in range(n) if not out[i]]