210. 课程表 II

现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
在这里插入图片描述

思路:
思路1:拓扑排序。构建的邻接表就是我们通常认识的邻接表,每一个结点存放的是后继结点的集合。
该方法的每一步总是输出当前无前趋(即入度为零)的顶点。为避免每次选入度为 0 的顶点时扫描整个存储空间,可设置一个队列暂存所有入度为 0 的顶点。
1、在开始排序前,扫描对应的存储空间,将入度为 0 的顶点均入队列。
2、只要队列非空,就从队首取出入度为 0 的顶点,将这个顶点输出到结果集中,并且将这个顶点的所有邻接点的入度减 1,在减 1 以后,发现这个邻接点的入度为 0 ,就继续入队。
3、最后检查结果集中的顶点个数是否和课程数相同即可。
class Solution(object):
    def findOrder(self, numCourses, prerequisites):
        """
        :type numCourses: int
        :type prerequisites: List[List[int]]
        :rtype: List[int]
        """
        plen = len(prerequisites)
        if plen == 0:    # 没有课程限制,肯定能完成
            return [i for i in range(numCourses)]
        in_degrees = [0 for _ in range(numCourses)]  # 入度:此结点之前的待完成的课程数
        adj = [set() for _ in range(numCourses)]    # 邻接表:此结点之后的课程
        for second, first in prerequisites:
            in_degrees[second] += 1
            adj[first].add(second)
        
        res = []
        queue = []
        for i in range(numCourses):   #首先遍历一遍,将入度为0的课程先加入队列
            if in_degrees[i] == 0:
                queue.append(i)
        
        while queue:
            top = queue.pop(0)
            res.append(top)
            
            for successor in adj[top]:   # 遍历此课程之后的课程
                in_degrees[successor] -= 1
                if in_degrees[successor] == 0:
                    queue.append(successor)
        if len(res) != numCourses:
            return []
        return res
思路2:构建逆邻接表,实现深度优先遍历。思路其实也很简单,其实就是检测这个有向图中有没有环,只要存在环,课程就不能完成。
注意:这个深度优先遍历得通过逆邻接表实现,当访问一个结点的时候,应该递归访问它的前驱结点,直至前驱结点没有前驱结点为止。
class Solution(object):

    def findOrder(self, numCourses, prerequisites):
        """
        :type numCourses: int 课程门数
        :type prerequisites: List[List[int]] 课程与课程之间的关系
        :rtype: bool
        """
        # 课程的长度
        clen = len(prerequisites)
        if clen == 0:
            # 没有课程,当然可以完成课程的学习
            return [i for i in range(numCourses)]

        # 逆邻接表
        inverse_adj = [set() for _ in range(numCourses)]
        # 想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
        # 1 -> 0,这里要注意:不要弄反了
        for second, first in prerequisites:
            inverse_adj[second].add(first)

        visited = [0 for _ in range(numCourses)]
        # print("in_degrees", in_degrees)
        # 首先遍历一遍,把所有入度为 0 的结点加入队列

        res = []
        for i in range(numCourses):
            if self.__dfs(i,inverse_adj, visited, res):
                return []
        return res

    def __dfs(self, vertex, inverse_adj, visited, res):
        """
        注意:这个递归方法的返回值是返回是否有环
        :param vertex: 结点的索引
        :param inverse_adj: 逆邻接表,记录的是当前结点的前驱结点的集合
        :param visited: 记录了结点是否被访问过,2 表示当前正在 DFS 这个结点
        :return: 是否有环
        """
        # 2 表示这个结点正在访问
        if visited[vertex] == 2:
            # DFS 的时候如果遇到一样的结点,就表示图中有环,课程任务便不能完成
            return True
        if visited[vertex] == 1:
            return False
        # 表示正在访问这个结点
        visited[vertex] = 2
        # 递归访问前驱结点
        for precursor in inverse_adj[vertex]:
            # 如果没有环,就返回 False,
            # 执行以后,逆拓扑序列就存在 res 中
            if self.__dfs(precursor, inverse_adj, visited, res):
                return True

        # 能走到这里,说明所有的前驱结点都访问完了,所以可以输出了
        # 并且将这个结点状态置为 1
        visited[vertex] = 1

        # 先把 vertex 这个结点的所有前驱结点都输出之后,再输出自己
        res.append(vertex)
        # 最后不要忘记返回 False 表示无环
        return False

参考 https://leetcode-cn.com/problems/course-schedule-ii/comments/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值