题目
你这个学期必须选修 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 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i]
中的所有课程对 互不相同
课程表问题的实现思路
这个问题可以看作是一个有向图的拓扑排序问题。我们需要判断在给定的课程和先修课程关系下,是否可以完成所有课程的学习。具体思路如下:
- 图的表示:我们将课程视为图中的节点,先修关系视为有向边。例如,如果课程 A 需要先修课程 B,则有一条从 B 指向 A 的边。
- 入度计算:我们需要计算每个课程的入度(即有多少课程指向它)。如果一个课程的入度为 0,说明可以直接学习。
- 拓扑排序:使用队列来实现拓扑排序。将所有入度为 0 的课程加入队列,然后逐一学习这些课程,并减少其后续课程的入度。如果某个后续课程的入度减为 0,则将其加入队列。
- 判断完成情况:如果我们能够学习的课程数量等于总课程数量,则返回
true
,否则返回false
。
C++ 实现
以下是使用 C++ 实现的代码:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<int> inDegree(numCourses, 0); // 记录每个课程的入度
vector<vector<int>> graph(numCourses); // 邻接表表示图
// 构建图和入度数组
for (const auto& prereq : prerequisites) {
int course = prereq[0];
int preCourse = prereq[1];
graph[preCourse].push_back(course); // preCourse -> course
inDegree[course]++;
}
// 初始化队列,将所有入度为0的课程加入队列
queue<int> q;
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
q.push(i);
}
}
int count = 0; // 记录可以学习的课程数量
// 拓扑排序
while (!q.empty()) {
int course = q.front();
q.pop();
count++;
// 遍历所有依赖于该课程的后续课程
for (int nextCourse : graph[course]) {
inDegree[nextCourse]--; // 减少后续课程的入度
if (inDegree[nextCourse] == 0) {
q.push(nextCourse); // 如果入度为0,加入队列
}
}
}
// 判断是否可以完成所有课程
return count == numCourses;
}
int main() {
int numCourses = 2;
vector<vector<int>> prerequisites = {{1, 0}};
cout << (canFinish(numCourses, prerequisites) ? "true" : "false") << endl;
numCourses = 2;
prerequisites = {{1, 0}, {0, 1}};
cout << (canFinish(numCourses, prerequisites) ? "true" : "false") << endl;
return 0;
}
代码说明
- 图和入度数组的构建:通过遍历
prerequisites
数组,构建图的邻接表和入度数组。 - 队列的使用:使用队列来处理所有入度为 0 的课程,进行拓扑排序。
- 课程计数:在拓扑排序过程中,记录可以学习的课程数量,最后与总课程数量进行比较。
复杂度
这道题的时间复杂度为 O(V+E),其中 V 表示课程数量,E 表示先修课程的数量。具体分析如下:
- 构建图和入度数组:遍历所有的先修课程关系,需要 O(E) 的时间。
- 拓扑排序:
- 初始化队列,将所有入度为 0 的课程加入队列,需要 O(V) 的时间。
- 从队列中取出课程并减少其后续课程的入度,每个课程最多进出队列一次,因此需要 O(V+E) 的时间。
- 判断是否可以完成所有课程:只需要比较学习的课程数量与总课程数量,时间复杂度为 O(1)。
综上所述,总的时间复杂度为 O(V+E)。