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())
{
// 每次从队列中获得节点,我们将该节点放在排序的末尾,
// 并且把它指向的课程的入度各减 1
int 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) 的空间存储节点入度、最终答案等。