你这个学期必须选修 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 <= 20000 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= ai, bi < numCoursesprerequisites[i]中的所有课程对 互不相同
思路
本题把课程看做节点,课程间依赖关系看做点之间的连线,这道题就是求有向图是否存在环,如果存在环,返回 false,不存在环返回 true。
有向图的遍历可以用深度优先遍历和广度优先遍历,现在我们用深度优先遍历,下篇用广度优先遍历。
我们遍历节点时,把节点定义为三种状态:
- 0:未遍历
- 1:遍历中
- 2:已遍历
节点的初始状态为0,开始遍历它及它的子节点设置为1,遍历完成后设置为2。开始遍历时我们先判断节点的状态
- 节点状态为0,表示节点未被遍历过。把节点状态设置为1,找到所有和他相连通的节点并遍历,遍历完成后状态修改为2,如果所有相连节点都返回true,本节点也返回true,否则返回false。
- 节点状态为2,表示节点已被遍历过,直接返回true。
- 节点状态为1,说明节点在本次深度遍历的过程中被遍历了2次,存在环,返回false。
解题
/**
* @param {number} numCourses
* @param {number[][]} prerequisites
* @return {boolean}
*/
var canFinish = function (numCourses, prerequisites) {
const status = Array(numCourses).fill(0);
const map = new Map();
for (let [a, b] of prerequisites) {
let arr = map.get(b);
if (!arr) {
map.set(b, (arr = []));
}
arr.push(a);
}
const deep = (start) => {
if (status[start] === 2) return true;
if (status[start] === 1) return false;
status[start] = 1;
const arr = map.get(start);
let res = true;
if (arr) {
for (let i = 0; i < arr.length; i++) {
if (!deep(arr[i])) {
res = false;
break;
}
}
}
status[start] = 2;
return res;
};
for (let key of map.keys()) {
if (!deep(key)) {
return false;
}
}
return true;
};