现在你总共有 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