《Leetcode》210、课程表II

14 篇文章 0 订阅

现在你总共有 n 门课需要选,记为 0 到 n-1。

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

给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

示例1

输入: 2, [[1,0]] 
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例2

输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
     因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

思路:刚开始拿到这个题是完全没有思路的,看了解析以后第一个讲解是DFS,我还是没有理解。第二种BFS的理念搞明白了,但是这个题的关键是先要构建一个有向图,并且把有向图的入度都存储起来。那么这个题的关键思想就是一个拓扑排序问题。拓扑排序的时候在考研王道数据结构上面有看过,用通俗的语言将就是:饭得一口一口吃,事得一件件干。说白了就是一个顺序问题,比如早上起床,先穿衣服,在洗脸刷牙,在吃早饭,然后上课。这个顺序是一个正常人正经的顺序,不能先上课在回去洗脸把,这有点反常理。在举一个例子,先上幼儿园,在上小学,在上初中,高中、大学.........;如果你要参加高考,那么必须得参加这个中考。也就是后面的时间必须有前面的驱动,那么自然而然前面的事件指向后面的事件。那么如果一个事件不依赖于任何的其它先决事件,也就是不由其它的事件进行驱动的话,那么它的顺序就是第1,可以理解为自我驱动。如果把这个事件放在图论中,那么就说明这个事件的入度是为0的,出度当然是不清楚的。所以优先顺序是和一个节点的入度有关系,如果入度为0的话那就按顺序加入结果集就好了。

首先我们需要构建一个有向无环图,当然了拓扑排序也是验证一个图有无环的一个重要方法。我们采用哈希表去存储这个图的数据结构。key:先导课程,value:参加的课程。然后在构建一个列表,这个列表用来存放每个节点的入度。然后在初始化一个结果列表。这个结果列表就是存储入度为0的节点的。其实给的二维列表就是一个邻接矩阵,我们可以根据这个邻接矩阵去画一下这个拓扑图。有时候算法比较复杂,但是画图就比较清晰明了。

解释一下这个图:根据例2画的图,如果你要修1和2,先行课程是0。如果要修3,先行课程是1或2。左边那幅图0的入度是0所以它的位置肯定的第一;中间这幅图,因为0已经输出了,因此2和1的入度变成0了,所以修完0就可以修2或者1,把这两个都加入结果;最后一幅图,孤苦伶仃,茕茕孑立,形影相吊。度为0了,那就直接可以加入到结果集中去了。所以整个算法的大致思路脉络就比较清晰了,把度为0的元素从加入队列,然后这个度为0的元素出队列之后。把它指向的元素的入度都减去 1。如果减完之后这些元素度为0,那么就添加到队列中,持续上述操作,然后知道最后的元素入度都为0。那把这些度为0的元素都加入到结果集中,整个过程就结束了。在代码部分会有比较详细的解释。

class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        # 存储有向图,key是list,其实就是每一个元素指向几个元素
        edges = collections.defaultdict(list)
        # 存储每个节点的入度,初始化的度都为0
        indeg = [0] * numCourses
        # 存储答案
        result = []
        # 构建有向图:key是先行课程,value是要修的课程
        # 存储所以节点的入度
        for info in prerequisites:
            edges[info[1]].append(info[0])
            indeg[info[0]] += 1
        print(edges)
        #defaultdict(<class 'list'>, {0: [1, 2], 1: [3], 2: [3]})
        #indeg列表的索引就是课程,列表的值就是这个课程的入度
        print(indeg)
        #[0, 1, 1, 2]  
        # 将所有入度为 0 的节点放入队列中
        q = collections.deque([u for u in range(numCourses) if indeg[u] == 0])

        while q:
            # 从队首取出一个节点
            u = q.popleft()
            # 放入答案中
            result.append(u)
            for v in edges[u]:
                #与这个节点相关的前行课程的度都必须要-1
                indeg[v] -= 1
                # 如果相邻节点 v 的入度为 0,就可以把这个节点加入到对列中去
                if indeg[v] == 0:
                    q.append(v)
        #如果最后返回的结果长度和课程的数目不一致就返回一个空列表
        if len(result) != numCourses:
            return []
        return result
        

总结:第一次写拓扑排序,还是多总结,多练习。之前没见过的算法,怎么想都没有思路的。之后把拓扑排序找个事件好好写一篇总结一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值