「前端刷题」207.课程表(MEDIUM)

607 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

题目(Course Schedule)

链接:leetcode-cn.com/problems/co… 解决数:1593 通过率:53.9% 标签:深度优先搜索 广度优先搜索 图 拓扑排序 相关公司:amazon microsoft facebook 你这个学期必须选修 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 <= 105 0 <= prerequisites.length <= 5000 prerequisites[i].length == 2 0 <= ai, bi < numCourses prerequisites[i] 中的所有课程对 互不相同

思路

  • 邻接表h表示学习某课程的需要课程。哈希表r表示状态1搜索中2已完成undefined未搜索
  • 邻接表h存学习某课程所需课程,哈希表r存状态1搜索中2已完成undefined未搜索
  • 课程总数数组与邻接表中存在的课程数组取差集
  • 遍历邻接表,从任意课程递归,遇课程看状态:
    • 搜索中, 存在,终止递归,返回false
    • 已完成,不做任何事,终止递归,返回true
    • 未搜索,搜索该课程所需课程
      • 课程无所需课程,标记已完成,已完成课程数+1
        • 结果集长度 = 课程总数,终止地址,返回false
      • 课程有所需课程,标记搜索中遍历搜索所需课程
        • 回溯所有所需课程都为已完成,该课程标记已完成,已完成课程数+1
  • 已学习课程 === 课程总数,返回false,终止递归。因为搜索中也会返回false,需再判断一次

代码

var canFinish = function(numCourses, prerequisites, h = Object.create(null), r = Object.create(null), n = 0) {
    prerequisites.forEach(v => h[v[0]] ? h[v[0]].push(v[1]) : h[v[0]] = [v[1]])
    var f = v => (r[v] = 2, ++n < numCourses), 
        d = v => r[v] === 1 ? false : r[v] || (h[v] ? (r[v] = 1, h[v].every(v => d(v)) && f(v)) : f(v))
    for (var k in h) if(d(k) === false) return n === numCourses
    return true
};

深度优先搜索 · 剪枝

解题思路

  • 邻接表h表示学习某课程的需要课程。哈希表r记录搜索经历的课程
  • 找到没有需要课程的课程,将r中课程放入m表示这些课程已完成(有办法学)
  • 继续递归,跳过已完成的课程。最后剩下没办法学(存在依赖)的课程加不到m

代码

var canFinish = function(numCourses, prerequisites, h = {}, m = []) {
    prerequisites.forEach(v => h[v[0]] ? h[v[0]].push(v[1]) : h[v[0]] = [v[1]])
    var d = (v, r) => r.includes(v) ? false : (m.includes(v) || h[v] === undefined) ? 
                     (m = m.concat(r), m.length === numCourses ? false : true) : 
                     (r.push(v), h[v].every(v => d(v, r.slice())))
    for (var k in h) if(d(Number(k), []) === false) return m.length === numCourses
    return true
};

广度优先搜索

解题思路

  • 邻接表h表示学习某课程的需要课程r表示完成某课程的可选课程
  • 根据h课程总数,找出不需要任何课程的课程,放入可学习队列q
  • 根据r,遍历完成某课程后的可选课程,根据h
    • 可选课程只需要完成某课程,该可选课程放入可学习队列q并从h删除
    • 可选课程还需完成其它课程,该可选课程需要课程中把某课程删除

代码

完整版

var canFinish = function(numCourses, prerequisites, h = Object.create(null), r = Object.create(null)) {
    prerequisites.forEach(v => (h[v[0]] ? h[v[0]].push(v[1]) : h[v[0]] = [v[1]], r[v[1]] ? r[v[1]].push(v[0]) : r[v[1]] = [v[0]]))
    for (var i = 0, q = [], n; i < numCourses; i++) !h[i] && q.push(i)
    while (q.length) 
        r[n = q.pop()] && r[n].forEach(v => {
            if (h[v].length === 1) {
                q.push(v)
                delete h[v]
            } else {
                h[v].splice(h[v].findIndex(v=> v === n), 1)
            }
        })
    return Object.keys(h).length === 0
};