你这个学期必须选修
numCourses
门课程,记为0
到numCourses - 1
。在选修某些课程之前需要一些先修课程。 先修课程按数组
prerequisites
给出,其中prerequisites[i] = [ai, bi]
,表示如果要学习课程ai
则 必须 先学习课程bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。请你判断是否可能完成所有课程的学习?如果可以,返回
true
;否则,返回false
。
解法1 dfs检测
思路
图论的建模一般使用邻接表或者邻接矩阵,而这题边比较少,所以适合邻接表。
首先对课表进行建模,建完之后去检测是否成环。
关键是如何判定成环呢?
我们可以使用一个数组来记录当前课表的状态,有三种状态:
0
表示正常1
表示正在访问2
表示已经访问完毕
在遍历课表的时候,去查询该课程邻接表里的状态,如果出现 1
则说明成环,直接返回 false
即可。遍历完毕说明正常,返回 true
。
代码
function canFinish(numCourses: number, prerequisites: number[][]): boolean {
const map = new Array(numCourses).fill(0).map(() => []);
for (let [to, from] of prerequisites) {
map[from].push(to);
}
const visited = new Array(numCourses).fill(0);
const hasCycle = (node) => {
if (visited[node] === 1) return true;
if (visited[node] === 2) return false;
visited[node] = 1;
for (let course of map[node]) {
if (hasCycle(course)) return true;
}
visited[node] = 2;
return false;
};
for (let i = 0; i < numCourses; i++) {
if (hasCycle(i)) return false;
}
return true;
};
时空复杂度
n
代表课程数,m
代表边数
时间复杂度:O(n + m)
空间复杂度:O(n + m)
解法2 拓扑排序
思路
还是需要先建图,同样使用邻接表。拓扑排序需要检查节点的入度,所以还需要一个数组来保存课程的入度。
当课程入度为 0
的时候,说明该课程没有前置课程,可以直接学习。在学习完这个课程之后,其后置课程的入度都需要减少,当后续课程的入度为 0
时,此时需要进入队列进行学习。
如何判定是否成环呢?已学习的课程是否等于课程数量,如果不等于,说明无法完成学习。
代码
function canFinish(numCourses: number, prerequisites: number[][]): boolean {
const map = new Array(numCourses).fill(0).map(() => []);
const inDegree = new Array(numCourses).fill(0);
for (let [to, from] of prerequisites) {
map[from].push(to);
inDegree[to]++;
}
const queue = [];
for (let i = 0; i < inDegree.length; i++) {
if (inDegree[i] === 0) {
queue.push(i);
}
}
let cnt = 0;
while (queue.length) {
cnt++;
const course = queue.shift();
for (let node of map[course]) {
inDegree[node]--;
if (inDegree[node] === 0) {
queue.push(node);
}
}
}
return cnt === numCourses;
};
时空复杂度
时间复杂度:O(n + m)
空间复杂度:O(n + m)