leetcode:802. 找到最终的安全状态

题目来源

题目描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
     
    }
};

题目解析

深度优先遍历 (官方答案)

根据题意,如果起始节点位于一个环内,或者能够到达一个环,则该节点不是安全的。否则,该节点就是安全的。

我们可以使用DFS来找环,并在深度优先搜索的时候,用三色对节点进行标记,标记的规则如下:

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

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

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

如果搜索过程中没有遇到灰色节点,则说明没有遇到环,那么递归返回前,我们将其标记由灰色改为黑色,即表示它是一个安全的节点。

class Solution {
    bool safe(vector<vector<int>>& graph, std::vector<int> &color, int x){
        if(color[x] > 0){ //该节点已经被访问过了
            return color[x] == 2;  // 判断该节点是否安全
        }
        color[x] = 1;  // 正在访问中
        for(int y : graph[x]){
            if(!safe(graph, color, y)){  // 只要有一个未被访问到
                return false;
            }
        }
        color[x] = 2;
        return true;
    }
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        int n = graph.size();
        std::vector<int> color(n);
        vector<int> ans;
        for (int i = 0; i < n; ++i) {
            if (safe(graph, color, i)) {
                ans.push_back(i);
            }
        }
        return ans;
    }
};

方向图 + 拓扑排序

分析:

  • 根据题意,对于某一个节点,如果它当前在某个环内,或者有可能走到某个环上,那么它就是不安全的,因为如果遇到环,就无法在有限步内到达终点。
  • 根据上面的分析,我们发现,最简单的安全点就是无路可走的终点(也即出度为 00 的节点)。而拓展到一般情况,如果一个节点所指向的点均为安全点,那么这个点也是安全点。如何提取出这些安全点呢?我们需要避开图中的环路,提到环路,我们会自然地想到拓扑排序
  • 拓扑排序是找到图中入度为 0的节点,以及仅由入度为 0 节点所指向的节点。 ,而本题是找到图中**出度为 0 的节点,以及仅指向出度为 0 节点的节点。**刚好是相反的情况,所以,我们将题目给定的有向图变为反图(也即有向边的起点、终点互换),那么所有安全点便可以通过拓扑排序来求解了

思路:

  • 从终点逆向推导,将指向终点的边都删除,删除后如果指向它的节点没有出边了,那么这个节点也可以作为终点继续寻找
  • 这个节点其实就是逆向拓扑排序,将没有出边的节点入队,删除指向当前节点的所有边,将没有出边的节点入队
  • 为结果需要严格递增排序,所以拓扑排序结束之后再统计 入度 为 0 的节点。
class Solution {
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        int n = graph.size();
        std::vector<int> degree(n);
        std::map<int, std::set<int>> map;
        // 从终端开始推导: 终点---> 起点
        for (int begin = 0; begin < graph.size(); ++begin) {
            degree[begin] = graph[begin].size();  // out
            for(auto end : graph[begin]){
                map[end].insert(begin);
            }
        }

        std::queue<int> queue;
        for (int i = 0; i < degree.size(); ++i) {
            if(degree[i] == 0){
                queue.push(i);  // out = 0
            }
        }

        std::set<int> set;
        while (!queue.empty()){
            auto top = queue.front(); queue.pop();
            set.insert(top);
            for(auto next : map[top]){
                degree[next]--;
                if(degree[next] == 0){
                    queue.push(next);
                }
            }
        }

        return {set.begin(), set.end()};
    }
};

深度优先遍历(我的答案)

class Solution {
    /*
    第1步:找出所有的终端节点: out == 0
    第2步:以每一个节点(g.nodes)为起点,进行BFS,判断是不是安全节点:
        安全节点: 所有可能的路径都可以到达终端节点
    
    DSF时哪些节点可能不是安全节点呢?
        - DFS发现了环,有两种情况:
            - 自环  
            - 普通环
        - 对于每一个节点的所有可能的下一条路径,只要有一条路径不是安全节点,那么就一定不是安全节点
    */
    std::map<int, bool> status;    // 是不是安全节点, 终端一定是安全节点

    // 当前节点是不是安全节点
    // i一定会有效
    bool process(std::vector<std::vector<int>> &graph, int i, std::set<int> set){
        // 当前节点已经判断过了
        if(status.count(i)){
            return status[i];
        }

        bool flag = true;
        auto load = graph[i];  // 当前节点可能的路
        for(auto l : load){
            // 自环是不是终端节点呢? in = 1, out = 1,一定不是终端节点
            if(l == i  || set.count(l)){  // 自环路 || 形成了环
                flag = false;
                break;
            }

            set.insert(l);
            if(!process(graph, l, set)){  // 只要有一条路不能到达终点
                flag = false;
                break;
            }
            set.erase(l);
        }
        return status[i] = flag;
    }
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        // 一定要先:找出所有的终端节点
        for(int i = 0; i < graph.size(); i++){
            if(graph[i].empty()){
                status[i] = true;   //g[i] -- > [....]  , i.out = g[i].size(); 因此如果为空的话,那么一定是终端节点
            }
        }

        // 然后枚举每一个作为起点,判断能不能全部到达终点
        for(int i = 0; i < graph.size(); i++){
            std::set<int> path{i};
            process(graph, i, path);
        }

        // 获取最终的答案
        std::vector<int> vec;
        for(auto iter : status){
            if(iter.second){
                vec.push_back(iter.first);
            }
        }

        return vec;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值