当前位置:   article > 正文

leetcode802. 找到最终的安全状态

找到最终的安全状态

在有向图中, 我们从某个节点和每个转向处开始, 沿着图的有向边走。 如果我们到达的节点是终点 (即它没有连出的有向边), 我们停止。

现在, 如果我们最后能走到终点,那么我们的起始节点是最终安全的。 更具体地说, 存在一个自然数 K, 无论选择从哪里开始行走, 我们走了不到 K 步后必能停止在一个终点。

哪些节点最终是安全的? 结果返回一个有序的数组

该有向图有 N 个节点,标签为 0, 1, …, N-1, 其中 N 是 graph 的节点数. 图以以下的形式给出: graph[i] 是节点 j 的一个列表,满足 (i, j) 是图的一条有向边。

示例:

输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]]
输出:[2,4,5,6]
  • 1
  • 2

这里是上图的示意图。

在这里插入图片描述

提示:

  • graph 节点数不超过 10000.
  • 图的边数不会超过 32000.
  • 每个 graph[i] 被排序为不同的整数列表, 在区间 [0, graph.length - 1] 中选取。

来源:力扣(LeetCode
链接:https://leetcode-cn.com/problems/find-eventual-safe-states
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

基本思想

图的深度优先遍历:

  • 由于图中可能存在环,遍历的过程中标记节点,如果遍历的过程中该节点曾经访问过,那么该路径上所有的节点都是不安全的
  • 定义两个数组,一个用来标记节点是否访问过,另一个标记节点是否安全
  • 递归终止条件:该节点是出度为0的节点,该节点已经标记过是否安全,该节点已经访问过
  • 递归过程:一次遍历该节点的所有邻接的边,如果从当前边出发,得到的结果是该节点不是安全节点,那就无需继续遍历其他邻接边
  • 由于该有向图不一定是连通图,所以在主调函数中需要遍历每个节点,如果节点未曾访问则从该节点dfs
class Solution {
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        if(graph.size() == 0)
            return vector<int>();
        vector<int> safe(graph.size(), 0);
        for(int i = 0; i < graph.size(); ++i){
            vector<int> visit(graph.size(), 0);
            if(safe[i] == 0)
                dfs(graph, safe, visit, i);
        }
        vector<int> res;
        for(int i = 0; i < safe.size(); ++i)
            if(safe[i] == 1)
                res.push_back(i);
        return res;
    }
private:
    bool dfs(vector<vector<int>>& graph, vector<int>& safe, vector<int>& visit, int s){
        if(graph[s].size() == 0 || safe[s] == 1){
            safe[s] = 1;
            return true;
        }
        if(safe[s] == -1 || visit[s] == 1)
            return false;
        
        bool res = true;
        
        visit[s] = 1;
        for(int i = 0; i < graph[s].size(); ++i){
            int t = graph[s][i];              
            res = dfs(graph, safe, visit, t);
            if(!res){
                safe[s] = -1;
                return false;
            }
            
        }
        visit[s] = 0;
        if(res)
            safe[s] = 1;
        else
            safe[s] = -1;
        return res;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

拓扑排序思想:

  • 如果该节点是出度为0的节点那么该节点一定是安全节点,将该节点以及与该节点相连的边从有向图中去掉,那么剩余的节点中出度为0的节点也一定是安全节点,那么重复上述过程,直到没有出度为0的节点时,说明再也没有安全节点了
  • 在程序中用三重 循环来实现
    最外层循环的意思是:当没有点是安全节点时,终止循环
    内两层循环是依次判断每一个节点是不是出度为0的节点
class Solution {
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        if(graph.size() == 0)
            return vector<int>();
        vector<int> safe(graph.size(), 0);
        bool flag = true;
        while(flag){
            flag = false;
            for(int i = 0; i < graph.size(); ++i){
                int t = 0;
                if(safe[i] == 0){
                    for(int j = 0; j < graph[i].size(); ++j){
                        if(safe[graph[i][j]])
                            ++t;
                        else
                            break;
                    }
                    if(t == graph[i].size()){
                        safe[i] = 1;
                        flag = true;
                    }
                }
            }
        }
        vector<int> res;
        for(int i = 0; i < safe.size(); ++i)
            if(safe[i])
                res.push_back(i);
        return res;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

为了便于在程序中处理,可以生成一个顶点的入边的二维数组,题目中给的是顶点的出边的二维数组

  • 首先生成顶点的入边的二维数组
  • 将出度为0的顶点保存在队列中
  • 删除指向队列中的点(以队列中的点为弧头)的有向边,如果删除该边以后,顶点的出度为0,那么将该点加入到队列中,直到队列为空
class Solution {
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        if(graph.size() == 0)
            return vector<int>();
        vector<vector<int>> rgraph(graph.size());
        vector<int> outdegree(graph.size());
        queue<int> q;
        vector<int> res;
        for(int i = 0; i < graph.size(); ++i){
            for(int j = 0; j < graph[i].size(); ++j){//转化为入边
                rgraph[graph[i][j]].push_back(i);
            }
            outdegree[i] = graph[i].size();//保存节点的出度
            if(graph[i].size() == 0)//保存度为0的节点
                q.push(i);
        }
        while(!q.empty()){
            int f = q.front();
            q.pop();
            res.push_back(f);
            for(int i = 0; i < rgraph[f].size(); ++i){
                --outdegree[rgraph[f][i]];//递减以该安全节点为弧头的节点的出度
                if(outdegree[rgraph[f][i]] == 0)//递减完,出度为0则加入安全队列
                    q.push(rgraph[f][i]);
            }
        }
        sort(res.begin(), res.end());
        return res;    
    }
    
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/995272
推荐阅读
相关标签
  

闽ICP备14008679号