leetcode:207. 课程表
题目
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi。
- 例如,先修课程对
[0, 1]表示:想要学习课程0,你需要先完成课程1。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。prerequisites[i] 中的所有课程对 互不相同。
示例:
输入:numCourses = 2, prerequisites = [[1,0]]输出:true解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]输出:false解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
解答 & 代码
拓扑排序 - 有向图的环检测算法 - BFS 深度优先遍历(邻接表、入度表)
class Solution {public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {// 邻接表,存储图结构(边)。eg. graph[i] = [j, k],代表存在边 ij,ikvector<vector<int>> graph(numCourses);// 入度数组,eg. indeg[i] 代表节点(课程) i 的入度vector<int> indeg(numCourses, 0);for(int i = 0; i < prerequisites.size(); ++i){int preCourse = prerequisites[i][1];int nextCourse = prerequisites[i][0];graph[preCourse].push_back(nextCourse);++indeg[nextCourse];}// 课程队列,存入入度为 0 的节点(课程)queue<int> courseQ;// 初始化,将所有入度为 0 的节点都存入队列for(int i = 0; i < numCourses; ++i){if(indeg[i] == 0)courseQ.push(i);}int count = 0; // 已遍历的节点个数// 开始 BFS 广度优先遍历节点while(!courseQ.empty()){// 当前遍历的节点int curCourse = courseQ.front();courseQ.pop();++count; // 已遍历的节点个数 +1// 删除以 curCourse 节点为起点的所有边(将对应的终点的入度 -1)for(int i = 0; i < graph[curCourse].size(); ++i){int nextCourse = graph[curCourse][i]; // 这条边的终点--indeg[nextCourse]; // 终点入度 -1// 如果终点入度变为 0,则存入队列if(indeg[nextCourse] == 0)courseQ.push(nextCourse);}}// 如果最终已遍历的节点数 = 课程总数,说明不存在循环,返回 true;否则返回 falsereturn count == numCourses;}};
复杂度分析:设图的节点数(即课程数)为 N,设边数 M
- 时间复杂度 O(N + M):遍历一个图需要访问所有节点和边
- 空间复杂度 O(N + M):
- 邻接表
graph长为 N,并且存储了 M 条边的数据,因此空间复杂度 O(N + M) - 入度数组
indeg空间复杂度 O(N) - 队列
courseQ空间复杂度 O(N)
- 邻接表
执行结果:
执行结果:通过执行用时:16 ms, 在所有 C++ 提交中击败了 88.57% 的用户内存消耗:12.8 MB, 在所有 C++ 提交中击败了 94.24% 的用户
