Tag
【拓扑排序】【2023-09-09】
题目来源
题目解读
在选修某些课程之前需要先学习某些课程,先学习的课程有数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
表示为学习课程 ai
之前要先学习课程 bi
。请你判断是否可以完成 numCourses
课程数的课程。
解题思路
方法一:拓扑排序
这是一道典型的拓扑排序问题。在学习某些课程之前需要先选修学习一些其他的课程,有先后顺序的限制,因此可以使用拓扑排序来解题。拓扑排序方法的基本步骤如下:
- 计算每个节点的入度;
- 将入度为
0
的节点加入到队列que
中; - 依次枚举
que
中的节点node
,将与node
相连的节点node2
的入度-1
,如果节点node2
的入度为0
,则将其加入到队列que
中;
在枚举队列中的节点时,对节点进行标记或者一些特殊处理从而解决相应的问题。在本题中,在枚举队列中的节点时,维护一个变量 cnts
对节点进行计数,如果枚举结束后 cnts != numCourses
则表明选修限制中存在环,即互为先决条件的情况。
在具体实现中,需要定义一个二维数组来建立课程之间的边,还需要一个一维数组 in
来记录各个课程的入度。首先,遍历课程关系数组 prerequisties
更新课程的度以及课程之间的边关系;接着,将度为 0
的课程编号存入队列 q
中;然后,依次枚举队列中的课程,并更新与当前枚举的课程连接的新的课程的入度 -1
,如果更新后新的课程入度为 0
,则将入队列中,并且在枚举队列中课程时,将计数变量 visited
+1
;最后,如果计算变量与课程数量有如下关系:visted = numCourses
,则表明可以完成所有课程的学习,否则不能。
实现代码
class Solution {
vector<vector<int>> edges; // 课程之间的边
vector<int> in; // 课程的入度
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
edges.resize(numCourses);
in.resize(numCourses);
for(auto prerequisite : prerequisites){
edges[prerequisite[1]].push_back(prerequisite[0]);
++ in[prerequisite[0]];
}
queue<int> q;
for(int i = 0; i < in.size(); ++i){
if(in[i] == 0){
q.push(i);
}
}
int visited = 0;
while(!q.empty()){
int u =q.front();
q.pop();
++ visited;
for(int i = 0; i < edges[u].size(); ++i){
-- in[edges[u][i]];
if(in[edges[u][i]] == 0){
q.push(edges[u][i]);
}
}
}
return visited == numCourses;
}
};
复杂度分析
时间复杂度: O ( n + m ) O(n+m) O(n+m),其中 n n n 为课程数, m m m 选修课程限制数组的长度。
空间复杂度: O ( n + m ) O(n+m) O(n+m),课程之间建立邻接表的空间复杂度为 O ( n + m ) O(n+m) O(n+m)。广搜中使用最多 O ( n ) O(n) O(n) 的队列空间。因此总空间复杂度为 O ( n + m ) O(n+m) O(n+m)。
知识回顾
拓扑排序的含义与应用可参考 广度优先搜索(5)之拓扑排序。
写在最后
如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。
如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。
最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。