207. 课程表 Medium
你这个学期必须选修 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 。这是不可能的。
解题思路
输入: 一个整数表示课程数 numCourses
,一个二维数组 prerequisites
表示前置课程要求
输出: 判断是否可以完成所有课程的学习
本题属于DFS 检测环类问题。
- 建图:把课程依赖关系转成邻接表,例如
b → a
表示 b 指向 a。 - DFS 遍历:
- 用 visited 标记访问状态:
- 0 = 未访问
- 1 = 正在访问(递归栈中)
- 2 = 已完成
- 在 DFS 过程中:
- 如果访问到 1 → 出现环,返回 false。
- 如果访问到 2 → 已处理过,返回 true。
- 遍历所有课程,确保每个节点都检查一遍。
- 用 visited 标记访问状态:
代码实现
python
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# 用字典存储图:key = 先修课程, value = 依赖它的后续课程列表
graph = {}
# 构建有向图
for a, b in prerequisites:
if b in graph:
graph[b].append(a)
else:
graph[b] = [a]
# 访问状态数组
# 0 = 未访问, 1 = 正在访问(递归栈中), 2 = 已完成
visited = [0] * numCourses
def dfs(course):
# 如果遇到正在访问的节点,说明有环
if visited[course] == 1:
return False
# 如果已经访问完成,直接返回 True
if visited[course] == 2:
return True
# 标记为正在访问
visited[course] = 1
# 遍历后继课程,递归检查
if course in graph:
for next in graph[course]:
if not dfs(next):
return False
# 所有后继课程都能完成,标记为已完成
visited[course] = 2
return True
# 逐一检查所有课程
for i in range(numCourses):
if not dfs(i):
return False # 如果发现环,直接返回 False
return True # 没有环,可以完成所有课程
javascript
var canFinish = function(numCourses, prerequisites) {
// 用邻接表存储图:key = 先修课程, value = 依赖它的后续课程列表
const graph = new Map();
for (let [a, b] of prerequisites) {
if (graph.get(b)) {
graph.get(b).push(a);
} else {
graph.set(b, [a]);
}
}
// visited 数组表示每个节点的访问状态
// 0 = 未访问, 1 = 正在访问(递归栈中), 2 = 已完成
const visited = new Array(numCourses).fill(0);
function dfs(course) {
// 如果节点正在访问 → 说明遇到环
if (visited[course] === 1) return false;
// 如果节点已经访问完成 → 直接返回 true
if (visited[course] === 2) return true;
// 标记为正在访问
visited[course] = 1;
// 遍历该课程的所有后续课程
if (graph.get(course)) {
for (let next of graph.get(course)) {
// 如果发现环,直接返回 false
if (!dfs(next)) return false;
}
}
// 所有后续课程检查完毕,标记为已完成
visited[course] = 2;
return true;
}
// 对每门课进行 DFS 检查
for (let i = 0; i < numCourses; i++) {
if (!dfs(i)) return false; // 如果有环,直接返回 false
}
// 没有环 → 可以完成所有课程
return true;
};
复杂度分析
时间复杂度:O(V + E) (课程数 + 先修关系数)
空间复杂度:O(V + E)