Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

leetcode207:课程表问题 #66

Open
sisterAn opened this issue Jun 17, 2020 · 2 comments
Open

leetcode207:课程表问题 #66

sisterAn opened this issue Jun 17, 2020 · 2 comments
Labels

Comments

@sisterAn
Copy link
Owner

sisterAn commented Jun 17, 2020

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]

给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

提示:

  • 输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵
  • 你可以假定输入的先决条件中没有重复的边
  • 1 <= numCourses <= 10^5

附赠leetcode地址:leetcode

@7777sea
Copy link

7777sea commented Jun 18, 2020

image

/**
 * @param {number} numCourses
 * @param {number[][]} prerequisites
 * @return {boolean}
 */
var canFinish = function(numCourses, prerequisites) {
    
    const graph={};
    const degree=new Array(numCourses).fill(0);

    // 采用邻接表来保存课程的依赖关系,并且记录被依赖课程的先导次数
    for(const it of prerequisites){
        if(graph[it[0]]===undefined) graph[it[0]]=[it[1]]
        else graph[it[0]].push(it[1])

        degree[it[1]]++;
    }
    
    //degree[i]为0,表示该课程不属于任何课程的先导课
    // 则需要将这些课程编号入栈
    const stack=[];
    for(let i=0;i<numCourses;i++){
        if(degree[i]===0) stack.push(i)
    }

    let cnt=0;
    while(stack.length){
        const c=stack.pop();
        cnt++;
        for(const pre of (graph[c]||[c])){

            // 注意下面degree[pre]可能为负数,但是不影响结果
            degree[pre]--;

            if(degree[pre]===0) {
                stack.push(pre)
            
            }
        }
        
    }
    
    return cnt===numCourses
};

@sisterAn
Copy link
Owner Author

这是一道经典的拓扑排序问题,所谓拓扑排序,举个例子:

比如吃自助火锅,有一套约定俗成的流程,首先先打开包装,然后放入粉、佐料、菜,然后在加水,最后盖上盖子;这是有一套先后顺序的,你不可能没打开包装就放佐料,也可以说这是有一套依赖关系的,盖盖子依赖加水,加水依赖放入粉、佐料、菜,继而依赖打开包装

这种关系通常使用有向图来表示,如果这套流程能够成功的帮助你最后吃到火锅(无环),那这种依赖顺序就是拓扑排序,即拓扑排序是针对有向无环图的

解法:广度优先遍历

例如:

输入: 5, [[4,2],[4,3],[2,0],[2,1]]

所以我们可以使用 邻接表 来表示有向图中各个节点的依赖关系,同时维护一个入度表,则入度表中入度为 0 的节点所表示的课程是可以立即开始学习的(没有先决条件条件或先觉条件已完成)

那么这题就很简单了:

  • 创建一个队列,并将临接表中所有入度为 0 的节点放入队列中
  • 若队列非空,则从队列中出队第一个节点,numCourse — (学习该课程),然后将将依赖该课程所有临接节点的入度减 1
    • 若减 1 后节点入度为 0,则该课程又是可立即学习课程,将该节点添加到队尾
    • 若减 1 后节点入度不为 0 ,则继续遍历下一节点
  • 当队列为空,检查 numCourses === 0 (所有课程是否全部学习结束)即可
let canFinish = function(numCourses, prerequisites) {
    // 如果没有先决条件,即所有的课程均没有依赖关系
    // 直接返回 true
    if (prerequisites.length === 0) {
        return true
    }

    // 维护入度表
    let inDegree = new Array(numCourses).fill(0)
    // 维护临接表
    let adj = new Map()
    
    for (let e of prerequisites) {
        inDegree[e[0]]++
        if(!adj.has(e[1])) adj.set(e[1], [])
        let vEdge = adj.get(e[1])
        vEdge.push(e[0])
    }

    let queue = []
    // 首先加入入度为 0 的结点
    for (let i = 0; i < numCourses; i++) {
        if (inDegree[i] === 0) {
            queue.push(i)
        }
    }

    while (queue.length > 0) {
        // 从队首移除
        var v = queue.shift() 
        // 出队一门课程
        numCourses--
        if(!adj.has(v)) continue
        // 遍历当前出队结点的所有临接结点
        for(let w of adj.get(v)) {
            inDegree[w]--
            if (inDegree[w] === 0) {
                queue.push(w)
            }
        }
    }
    return numCourses === 0
}

复杂度分析:

  • 时间复杂度:O(V+E),遍历一个图需要访问所有节点和边
  • 空间复杂度:O(V+E),用于存储临接表等

leetcode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants