Every day a Leetcode
题目来源:210. 课程表 II
解法1:
什么是拓扑排序?
我们考虑拓扑排序中最前面的节点,该节点一定不会有任何入边,也就是它没有任何的先修课程要求。当我们将一个节点加入答案中后,我们就可以移除它的所有出边,代表着它的相邻节点少了一门先修课程的要求。如果某个相邻节点变成了「没有任何入边的节点」,那么就代表着这门课可以开始学习了。按照这样的流程,我们不断地将没有入边的节点加入答案,直到答案中包含所有的节点(得到了一种拓扑排序)或者不存在没有入边的节点(图中包含环)。
上面的想法类似于广度优先搜索,因此我们可以将广度优先搜索的流程与拓扑排序的求解联系起来。
算法:
代码:
/** @lc app=leetcode.cn id=210 lang=cpp** [210] 课程表 II*/// @lc code=start// 拓扑排序(广度优先搜索)class Solution
{
public:vector<int> findOrder(int numCourses, vector<vector<int>> &prerequisites){// 邻接矩阵vector<vector<int>> graph(numCourses, vector<int>());// 入度数组vector<int> inDegree(numCourses, 0);vector<int> ans;// 初始化邻接矩阵和入度数组for (vector<int> &p : prerequisites){graph[p[1]].push_back(p[0]);inDegree[p[0]]++;}queue<int> q;// 把入度为 0 的节点(即没有前置课程要求)放在队列中for (int i = 0; i < inDegree.size(); i++)if (inDegree[i] == 0)q.push(i);while (!q.empty()){// 每次从队列中获得节点,我们将该节点放在排序的末尾,// 并且把它指向的课程的入度各减 1int u = q.front();q.pop();ans.push_back(u);for (auto &v : graph[u]){inDegree[v]--;// 有课程的所有前置必修课都已修完(即入度为 0),// 我们把这个节点加入队列中if (inDegree[v] == 0)q.push(v);}}// 当队列的节点都被处理完时,说明所有的节点都已排好序,// 或因图中存在循环而无法上完所有课程for (int &in : inDegree){// 不可能完成所有课程if (in != 0)return {};}return ans;}
};
// @lc code=end
结果:
复杂度分析:
时间复杂度: O(n+m),其中 n 为课程数,m 为先修课程的要求数。这其实就是对图进行广度优先搜索的时间复杂度。
空间复杂度: O(n+m)。题目中是以列表形式给出的先修课程关系,为了对图进行广度优先搜索,我们需要存储成邻接表的形式,空间复杂度为 O(n+m)。在广度优先搜索的过程中,我们需要最多 O(n) 的队列空间(迭代)进行广度优先搜索,并且还需要若干个 O(n) 的空间存储节点入度、最终答案等。