【leetcode】207. 课程表

40 阅读1分钟

leetcode-207.png

这道题的本质是判断:是否可以完成所有课程,即判断图中是否存在环。
换句话说:从图中的某个节点出发,是否能够遍历完整个有向图,不形成环。

为了解决这个问题,我们可以使用拓扑排序(Kahn’s Algorithm)
思路如下:

  • 使用一个邻接表 graph 表示课程之间的依赖关系;
  • 使用一个入度数组 indegree 统计每门课程的前置课程数量;
  • 将所有入度为 0 的课程加入队列(这些课程没有依赖,可以先学);
  • 每学习一门课程,就将其邻接课程的入度减 1;
  • 如果某门课程的入度变为 0,说明可以开始学习,继续加入队列;
  • 最终如果遍历过的课程数等于总课程数,说明可以完成所有课程;否则图中存在环,无法完成。
var canFinish = function (numCourses, prerequisites) {
    let graph = new Map();
    let indegree = new Array(numCourses).fill(0);

    // 构建邻接表以及入度统计数组
    for (let [after, pre] of prerequisites) {
        indegree[after]++;
        if (!graph.has(pre)) graph.set(pre, []);
        graph.get(pre).push(after);
    }

    // indegree = 0
    let queue = [];
    for (let i = 0; i < numCourses; ++i) {
        if (indegree[i] === 0) queue.push(i);
    }

    let cnt = 0;
    while (queue.length) {
        let current = queue.shift();
        cnt++;
        // 存在邻接表的课程(学完current之后,还有得学
        if (graph.get(current)) {
            for (let next of graph.get(current)) {
                // 减少current课程所链接的下一门课程的入度
                indegree[next]--;
                // 入度为0,表示可以从这门课程入手了
                if (indegree[next] === 0) {
                    queue.push(next);
                }
            }
        }
    }
    return cnt === numCourses;
};

Tips:

  • 出现环的标志就是队列无法消除所有课程的入度,导致某些课程永远入度不为 0。
  • 例如依赖关系 [5, 5] 会导致课程 5 依赖自己,产生自环。