)@[TOC]([LeetCode解题报告] 207. 课程表 )
一、 题目
1. 题目描述
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]] 输出:true 解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]] 输出:false 解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 105
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i]
中的所有课程对 互不相同
Related Topics
- 深度优先搜索
- 广度优先搜索
- 图
- 拓扑排序
- 👍 1279
- 👎 0
二、 解题报告
1. 思路分析
这题是拓扑排序模板题。
课程的依赖关系是一个有向图。
拓扑排序从入度为0的所有点开始遍历,进不到环。
2. 复杂度分析
最坏时间复杂度O(n+m),n、m分别是有向图的节点数、边数。
3. 代码实现
拓扑排序
。
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# 首先如果出现[1,2],[1,3]代表必须2、3都学了才能学1
## 对课程进行拓扑排序,没遍历到的点一定在环上,
# 即入环的第一门课下图(3)需要两个依赖,而至少有一个依赖是在循环里的,访问不到
"""
9 ← 8
↓ ↑
1→2→(3)→4→5→6→7
"""
graph = collections.defaultdict(list) # 建图
# indegree = collections.defaultdict(int) # 这个是错的,因为不是所有点都在图里,所以入度需要硬性初始化,本体有numCourse门课。
indegree = [0]*numCourses # 计算所有点的入度
for v,u in prerequisites:
graph[u].append(v) # 建图
indegree[v] += 1 # 计算所有点的入度
indegree[u] += 0 # 计算所有点的入度
q = deque([u for u,x in enumerate(indegree) if x == 0]) # 把所有入度为0的点放入队列
visited = set(q) # 把所有在队列的点标记已访问,这同时表示能达到的课程
# 下面进行拓扑排序
while q:
u = q.popleft() # 取出队列中的点
for v in graph[u]:
indegree[v] -= 1 # u遍历到的所有点v,入度-1
if indegree[v] ==0: # 如果v的入度变成0,则放入队列。
visited.add(v)
q.append(v)
return len(visited) >= numCourses # 访问过的点就是能达到的点
三、 本题小结
拓扑排序,是基于有向图的一种遍历算法。
大致流程就是:
1) 对每条边 u→v,对 vv 的入度加一;
2) 遍历所有入度为 0 的点放入队列;
3) 取出队列中的点 ss,将它遍历到的边 s→t 中的 t 的入度减一;如果入度减到零,则将 t 放入队列;
4) 遍历完毕,如果还剩节点没遍历到,那么这图不能被拓扑排序。
这就是经典的拓扑排序算法。
拓扑排序经常用来处理依赖关系。