题目描述
你这个学期必须选修 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。这是不可能的。
思路&js代码
1、广度优先遍历
var canFinish = function (numCourses, prerequisites) {
const dependsArr = new Array(numCourses).fill(0); // 每个课程的依赖的数量
const map = {}; // 邻接表,存储 课程以及依赖该课程的课程
for (let i = 0; i < prerequisites.length; i++) {
const [a, b] = prerequisites[i]
dependsArr[a]++; // 求课的初始入度值
map[b] = map[b] ? [...map[b], a] : [a]
}
const queue = []; // 所有已经没有依赖的课程
for (let i = 0; i < dependsArr.length; i++) { // 所有入度为0的课入列
if (dependsArr[i] == 0) queue.push(i);
}
let count = 0;
while (queue.length) {
const selected = queue.shift(); // 当前选的课,出列
count++; // 选课数+1
const nextCourses = map[selected]; // 获取这门课对应的后续课
if (!nextCourses || !nextCourses.length) continue // 没有后续课
// 确实有后续课
for (let i = 0; i < nextCourses.length; i++) {
dependsArr[nextCourses[i]]--; // 依赖它的后续课的入度-1
if (dependsArr[nextCourses[i]] == 0) { // 如果因此减为0,入列
queue.push(nextCourses[i]);
}
}
}
return count == numCourses; // 选了的课等于总课数,true,否则false
};2、深度优先遍历
对于每个节点 x,都定义三种颜色值(状态值):
0:节点 x 尚未被访问到。 1:节点 x 正在访问中,dfs(x) 尚未结束。 2:节点 x 已经完全访问完毕,dfs(x) 已返回。
⚠误区:不能只用两种状态表示节点「没有访问过」和「访问过」。例如上图,我们先 dfs(0),再 dfs(1),此时 1 的邻居 0 已经访问过,但这并不能表示此时就找到了环。
算法流程:
- 建图:把每个 prerequisites[i]=[a,b] 看成一条有向边 b→a,构建一个有向图 g。
- 创建长为 numCourses 的颜色数组 colors,所有元素值初始化成 0。
- 遍历 colors,如果 colors[i]=0,则调用递归函数 dfs(i)。
- 执行 dfs(x): 1. 首先标记 colors[x]=1,表示节点 x 正在访问中。 2. 然后遍历 x 的邻居 y。如果 colors[y]=1,则找到环,返回 true。如果 colors[y]=0(没有访问过)且 dfs(y) 返回了 true,那么 dfs(x) 也返回 true。 3. 如果没有找到环,那么先标记 colors[x]=2,表示 x 已经完全访问完毕,然后返回 false。
- 如果 dfs(i) 返回 true,那么找到了环,返回 false。
- 如果遍历完所有节点也没有找到环,返回 true。
var canFinish = function(numCourses, prerequisites) {
const g = Array.from({ length: numCourses }, () => []);
for (const [a, b] of prerequisites) {
g[b].push(a);
}
const colors = Array(numCourses).fill(0);
// 返回 true 表示找到了环
function dfs(x) {
colors[x] = 1; // x 正在访问中
for (const y of g[x]) {
if (colors[y] === 1 || colors[y] === 0 && dfs(y)) {
return true; // 找到了环
}
}
colors[x] = 2; // x 完全访问完毕
return false; // 没有找到环
}
for (let i = 0; i < numCourses; i++) {
if (colors[i] === 0 && dfs(i)) {
return false; // 有环
}
}
return true; // 没有环
};