跳到主要内容

210. 课程表 II

210. 课程表 II

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

现在你总共有 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 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
  • 拓扑排序也可以通过 BFS 完成。

通过次数38,443 | 提交次数75,898

Second Try

2020-06-29

DFS版本,也是挺特别的思路,看题解才了解到的。需要额外提供markers标记,类似回溯法,但是竟然用了三个状态进行标记,想明白了这状态才是重点。状态0是还没被找过,2代表该点及其下游都是安全的无环点,1代表正在寻找过程中。因此如果重新遇到标记为1的点,说明遇到环路了,直接挂掉了。其次每次查找完,都是DFS状态末尾最后一个点先放进结果列表,因此返回的时候需要返回逆序表即可。

都挺有意思的,经此一役,算是直接解决了DAG有向无环图的拓扑排序问题,airflow的核心算法终于算是清楚了。

class Solution(object):
def findOrder(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: List[int]
"""
# 来个DFS版本,感觉比BFS+indegree版本的绕多了

graph = defaultdict(set)
indegree = [0 for i in range(numCourses)]
for (curr, prepreq) in prerequisites:
graph[prepreq].add(curr)
indegree[curr] += 1
starts = []
for idx in range(len(indegree)):
if indegree[idx] == 0:
starts.append(idx)

markers = [0 for i in range(numCourses)]
rv = []
valid = True
def dfs(node, markers, rv):
markers[node] = 1
for n in graph[node]:
# 下游还没搞定,又出现了一次,陷入循环中
if markers[n] == 1:
valid = False
return # 有没有return都不影响了,但可以加快
# 下游都是safe的,直接跳过
elif markers[n] == 2:
continue
# 状态未知,深入看一波
else:
dfs(n, markers, rv)
# 所有下游都搞定了,可以认为从该点往下游都是安全的
markers[node] = 2
rv.append(node)

for node in starts:
dfs(node, markers, rv)

if not valid or len(rv) != numCourses:
return []

return rv[::-1]

  • 执行用时:32 ms, 在所有 Python 提交中击败了71.76%的用户
  • 内存消耗:16.3 MB, 在所有 Python 提交中击败了100.00%的用户

First Try

2020-06-29

BFS版本+ indegree表,终于掌握了拓扑排序,喜大普奔~

不过airflow的顺序法应该不是这种排序模型,现在的排法有点左右横跳了。。。

通过0207题知道了indegree表和graph表示方法的概念后,做这种题还真是顺手了。

class Solution(object):
def findOrder(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: List[int]
"""
# 感觉使用indegress表比较方便
indegree = [0 for i in range(numCourses)]
graph = defaultdict(set)
for (curr, prepreq) in prerequisites:
indegree[curr] += 1
graph[prepreq].add(curr)
# 写出了bug版本: queue = [i for i in indegree if i == 0]
queue = []
for idx in range(len(indegree)):
if indegree[idx] == 0:
queue.append(idx)
seq = []
while(queue):
node = queue.pop(0)
seq.append(node)
for n in graph[node]: # 某些node压根没在graph中表示,则返回empty set
indegree[n] -= 1
if indegree[n] == 0:
queue.append(n)
if len(seq) != numCourses:
return []
return seq
  • 执行用时:24 ms, 在所有 Python 提交中击败了94.12%的用户
  • 内存消耗:14.2 MB, 在所有 Python 提交中击败了100.00%的用户