leetcode题解:第210题Course Schedule II

这篇博客详细解析了LeetCode第210题Course Schedule II的解题思路,主要涉及图论中的拓扑排序问题。作者介绍了两种解法:BFS和DFS,并提供了相应的代码实现。在BFS中,通过队列来寻找入度为0的节点,DFS则利用深度优先搜索来避免环的存在。文章强调了邻接表在提高效率方面的重要性,并指出题目要求的特殊情况——输出顺序与常规拓扑排序相反。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://leetcode-cn.com/problems/course-schedule-ii

分析

这道题是典型的图论的算法题,主题就是拓扑排序:把图中的所有节点排序,使得对于所有的边<u, v>,u在线性序列中出现在v之前。

一般来说,要做拓扑排序,有两种解法:

  1. 遍历图,找到所有入度为0的节点(源),然后删去这个节点以及从它出去的边,将该节点加入线性序列,重复这个过程,这属于BFS
  2. 使用DFS,对于所有尚未被搜索的u,当u没有对应的v或者v都已经被搜索完成时(可看作此时u的出度为0),将u加入线性序列,u标记为搜索完成。

BFS得到的序列是拓扑排序的正序,DFS得到的序列是拓扑排序的反序

此外,如果图不是DAG,图中有环的话,是没有拓扑排序的,这很容易理解,而上面两种方法也有不同的判断是否有环的方法:

  1. 在BFS过程中,如果在剩下的节点集合中找不到源,剩余节点数又大于0,就有环
  2. 在DFS过程中,节点的标记不仅有尚未搜索搜索完成,还有一种状态:搜索中,对于一个u,如果它的某个v处于搜索中,那么就遇到了环

注意
这两种解法都需要对一个节点u,快速获取到它的所有v,也即获取到从它出发的所有边。因此,事先建立用邻接表表示的图是很有必要的。
如果使用二维数组来实现,效率会高,但是会浪费很多空间;相比之下,二维的STL容器vector就显得非常好用了,第一维表示u,第二维表示v,这就建立了邻接表。

解法一、BFS

我们首先要计算所有节点的入度in_degree,然后重复以下过程:

  • 找到入度为0的节点,把它加入到线性序列的尾部
  • 删除入度为0的节点,这在代码中体现为:对于所有从它到达的v,v的入度减一
  • 找不到源时说明图中有环,不存在拓扑排序

如何快速获取到u的所有边我们已经在分析中阐述了,那么如何快速获取到入度为0的节点呢?我们可以用一个队列来保存所有入度为0的节点,每次取出一个,当v的入度减一变为0时,v又可以进入队列。这样,获取入度为0的节点仅需 O ( 1 ) O(1) O(1)的时间。

代码
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> in_degree(numCourses, 0);
        vector<vector<int>> edges(numCourses, vector<int>());
        for (int i = 0; i < prerequisites.size(); ++i) {
            int v = prerequisites[i][0], u = prerequisites[i][1];
            edges[u].push_back(v);
            in_degree[v]++;
        }
        queue<int> q;
        vector<int> order;
        for (int i = 0; i < numCourses; ++i) if (in_degree[i] == 0) q.push(i);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            order.push_back(u);
            for (int i = 0; i < edges[u].size(); ++i) {
                int v = edges[u][i];
                if (--in_degree[v] == 0) q.push(v);
            }
        }
        return order.size() == numCourses ? order : vector<int>();
    }
};

注意代码中构造邻接表时我交换了u和v的定义,这是由于题目的特定要求,[1,0]要求0输出在前,这说明其实构造的边u,v = 0,1

解法二、DFS

使用DFS的解法要更加简单一些,只是搜索中这个状态的定义非常重要,因为图中节点的入度大于1并不代表存在回路,只有当u尚未搜索完成,而从它深度搜索到的某个v又指向了它时,才存在回路。

代码
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> in_degree(numCourses, 0);
        vector<vector<int>> edges(numCourses, vector<int>());
        for (int i = 0; i < prerequisites.size(); ++i) {
            int u = prerequisites[i][0], v = prerequisites[i][1];
            edges[u].push_back(v);
            in_degree[v]++;
        }
        vector<int> visited(numCourses, 0);
        vector<int> order;
        for (int i = 0; i < numCourses; ++i) {
            if (visited[i] == 0) visited[i] = 1, dfs(i, visited, edges, order);
        }
        return acyclic ? order : vector<int>();
    }
private:
    bool acyclic = true;
    void dfs(int u, vector<int>& visited, vector<vector<int>>& edges, vector<int>& order) {
        for (int i = 0; i < edges[u].size(); ++i) {
            int v = edges[u][i];
            if (visited[v] == 1) {
                acyclic = false;
                return;
            }
            else if (visited[v] == 0) visited[v] = 1, dfs(v, visited, edges, order);
        }
        visited[u] = 2;
        order.push_back(u);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值