❓207. 课程表
难度:中等
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
例如,先修课程对 [0, 1]
表示:想要学习课程 0
,你需要先完成课程 1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
- 1 < = n u m C o u r s e s < = 1 0 5 1 <= numCourses <= 10^5 1<=numCourses<=105
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i]
中的所有课程对 互不相同
💡思路:拓扑排序(BFS)
可以将选修课程先后顺序看作有向图的边:
- 只要该有向图不存在环,就能完成所有课程的学习。
- 以下是判断有向图是否存在环的基本步骤:
- 统计每个节点的入度(即指向该节点的边的数量),并将入度为
0
的节点加入到一个队列中。 - 从队列中取出一个节点,并将其从图中删除,同时将其所有指向的节点的入度减
1
。如果某个节点的入度减为0
,则将其加入队列。 - 重复步骤2,直到队列为空。
- 如果在执行拓扑排序的过程中,存在某个节点的入度始终不为
0
,则说明存在环,否则该有向图为DAG。
- 统计每个节点的入度(即指向该节点的边的数量),并将入度为
相同题解的题目:210. 课程表 II
🍁代码:(Java、C++)
Java
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<Integer>[] courList = new List[numCourses];
for (int i = 0; i < numCourses; i++) {
courList[i] = new ArrayList<>();
}
int[] inNum = new int[numCourses];//每个课程的入度数
for(int[] p : prerequisites){//找到所有该前修课程之后的课程
courList[p[1]].add(p[0]);
inNum[p[0]]++;
}
Queue<Integer> q = new LinkedList<Integer>();//存储所有入度为0的课程
for (int i = 0; i < numCourses; i++) {
if (inNum[i] == 0) {
q.offer(i);
}
}
while(!q.isEmpty()){//删除入度为0的点
int curNum = q.poll();
for(int it : courList[curNum]){
if(--inNum[it] == 0) q.offer(it);
}
courList[curNum].clear();
}
for(int num : inNum){//如果还存在入度不为0的点,则一定存在环
if(num != 0) return false;
}
return true;
}
}
C++
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<list<int>> courList(numCourses);
vector<int> inNum(numCourses, 0);//每个课程的入度数
for(auto p : prerequisites){//找到所有以该课程为前修课程的课程
courList[p[1]].push_back(p[0]);
inNum[p[0]]++;
}
queue<int> q;//存储所有入度为0的课程
for (int i = 0; i < numCourses; i++) {
if (inNum[i] == 0) {
q.push(i);
}
}
while(!q.empty()){//删除入度为0的点
int curNum = q.front();
q.pop();
auto it = courList[curNum].begin();
while(it != courList[curNum].end()){
if(--inNum[*it] == 0) q.push(*it);
it++;
}
courList[curNum].clear();
}
for(int num : inNum){//如果还存在入度不为0的点,则一定存在环
if(num != 0) return false;
}
return true;
}
};
🚀 运行结果:
🕔 复杂度分析:
- 时间复杂度:
O
(
n
+
m
)
O(n + m)
O(n+m),其中
n
为课程数,m
为先修课程的要求数。 - 空间复杂度: O ( n + m ) O(n + m) O(n+m)。
题目来源:力扣。
放弃一件事很容易,每天能坚持一件事一定很酷,一起每日一题吧!
关注我 leetCode专栏,每日更新!