冉宝的每日一题--8月5日

今天看女乒天团的直播,她们好有趣啊

802.找到最终的安全状态

在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。

对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是 安全 的。

返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按 升序 排列。

该有向图有 n 个节点,按 0 到 n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。

请添加图片描述

class Solution:
    def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
        num_nodes = len(graph)

        visited = [False for i in range(num_nodes)]
        not_save_nodes = set()

        def dfs(node,pre_list):
            nonlocal not_save_nodes
            # 处理当前节点,标记为已访问
            if node in pre_list or (node in not_save_nodes):
                not_save_nodes |= set(pre_list)
                return # 这条路径应该已经结束了
            visited[node] = True
            # 遍历与当前顶点所有为访问节点
            next_nodes = [next_n for next_n in graph[node]]
            for next_n in next_nodes:
                pre_next = pre_list[:] + [node]
                dfs(next_n,pre_next)
            
        for node in range(num_nodes):
            if not visited[node]:
                dfs(node,[])
        return [node for node in range(num_nodes) if node not in not_save_nodes]

dfs思路:

dfs(dep,)
{
	if (找到解||找不下去) {return }
	枚举下一种情况:
		dfs(dep+1,)
}
dfs(顶点)
{
	处理当前结点,记录为已访问
	遍历与当前定点相邻所有未访问结点
	{
		标记更改
		dfs(下一结点)
	}
}

官方题解:

方法一:深度优先搜索+三色标记法

根据题意,若起始节点位于一个环内,或者能到达一个环,则该节点不是安全的。
我们可以使用深度优先搜索来找环,并在深度优先搜索时,用三种颜色对节点进行标记,标记的规则如下:

  • 白色(用0表示):该节点尚未被访问;
  • 灰色(用1表示):该节点位于递归栈中,或者在某个环上;
  • 黑色(用2表示):该节点搜索完毕,是一个安全节点

当我们首次访问一个节点时,将其标记为灰色,并继续搜索与其相连的节点。

如果在搜索过程中遇到了一个灰色节点,则说明找到了一个环,此时退出搜索,栈中的节点仍然保持灰色,这一做法可以将【找到了环】这一信息传递到栈中的所有节点上。

如果搜索过程中没有遇到灰色节点,则说明没有遇到环,递归返回之前,将标记由灰色改成黑色表示是一个安全的结点。

from typing import List 
class Solution:
    def eventualSafeNodes(self,graph:List[List[int]])->List[int]:
        n = len(graph)
        color = [0] * n
        
        def safe(x:int)->bool:
            if color[x] > 0:
                return color[x] == 2 
            color[x] = 1 
            for y in graph[x]:
                if not safe(y):
                    return False
            color[x] = 2 
            return True
        
        return [i for i in range(n) if safe(i)]

复杂度分析:

  • 时间复杂度: O(n+m), 其中n是图中的点数,m是图中的边数
  • 空间复杂度:O(n),存储节点颜色和递归栈的开销均为O(n)

方法二:拓扑排序

根据题意,如果一个节点没有出边,就是安全的;
一个节点出边相连的点都是安全的,则该节点也是安全的。

根据这个性质,我们可以将图中所有的边反向,得到一个反图,在反图上进行拓扑排序。

拓扑排序的知识以及模版

拓扑排序定义: 对于给定的一张有向无环图,如果一个序列A满足:任意一条边(x,y),x都在y的前面 , 那么序列A 就是图的拓扑排序。

理解: 每一个状态没有循环依赖 --> 没有后效性 --> 可以满足DP递推
即: 有向无环图 ⇒ 拓扑排序 ⇒ 拓扑图 ⇒ 每个状态没有循环依赖 ⇒ 没有后效性,可以通过DP寻找最短、最长路径。

拓扑排序算法实质上属于广度优先遍历。包含以下几个步骤:
1- 从 有向图 中找一个没有前驱的节点 v ,如果v不存在说明不可以进行拓扑排序【图是个环图】,结束。
2- 将 v 输出;
3- 将v 从图中删除,同时删除关联v的所有节点。
4- 如果图中所有节点都输出,就结束,拓扑排序结束,否则转到1继续进行。

常见题型可以分为:

  • (1)Topsort模版
  • (2)判断是否有环
  • (3)已有编号求字典序最小的拓扑需
  • (4)给所有节点分配编号使得拓扑排序最小

参考资料

1- 模版:拓扑排序 https://fanfansann.blog.csdn.net/article/details/107707044

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值