课程表

97 阅读1分钟

207. 课程表 - 力扣(LeetCode)

你这个学期必须选修 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] 中的所有课程对 互不相同

思路

本题把课程看做节点,课程间依赖关系看做点之间的连线,这道题就是求有向图是否存在环,如果存在环,返回 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;
};