799 字
4 分钟
课程表

题目描述#

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

解题思路#

  1. 建图:将课程依赖关系转化为有向图(邻接表)
  2. 统计入度:记录每个节点的入度数(前置课程数)
  3. BFS初始化:所有入度为0的课程入队(可直接学习的课程)
  4. 拓扑排序
    • 出队课程,减少相邻节点入度
    • 若相邻节点入度归零则入队
  5. 验证结果:若处理课程数=总课程数 → 可完成

关键洞察#

  1. 环检测:若存在环则拓扑排序无法完成(队列提前空)
  2. BFS核心:入度为零的课程代表当前可学,是拓扑排序的起点 ,是拓扑排序的起点
  3. 贪心思想:总是优先处理无依赖的课程
  4. 图建模:将课程依赖抽象为有向边(如[1,0]表示0→1的边)

复杂度分析#

  • 时间复杂度:O(V+E)
    • 建图O(E) + BFS遍历所有节点和边O(V+E)
  • 空间复杂度:O(V+E)
    • 邻接表O(E) + 入度数组O(V) + 队列O(V)

代码实现#

递归版本

/**
* @param {number} numCourses
* @param {number[][]} prerequisites
* @return {boolean}
*/
var canFinish = function (numCourses, prerequisites) {
const edges = Array.from({
length: numCourses
}, () => []);
for (const [a, b] of prerequisites) {
edges[b].push(a);
}
const visited = new Array(numCourses).fill(0);
const dfs = (u) => {
visited[u] = 1;
for (const v of edges[u]) {
if (visited[v] === 0) {
if (!dfs(v)) return false;
} else if (visited[v] === 1) {
return false;
}
}
visited[u] = 2;
return true;
}
for (let i = 0; i < numCourses; i++) {
if (visited[i] === 0) {
if (!dfs(i)) return false;
}
}
return true;
};

迭代版本

const canFinish = (numCourses, prerequisites) => {
// 1. 初始化图和入度数组
const graph = Array.from({ length: numCourses }, () => []);
const inDegree = new Array(numCourses).fill(0);
// 2. 构建图结构并计算入度
for (const [course, prereq] of prerequisites) {
graph[prereq].push(course); // 前置课程→当前课程的边
inDegree[course]++; // 当前课程入度+1
}
// 3. 初始化队列(入度为0的节点)
const queue = [];
for (let i = 0; i < numCourses; i++) {
if (inDegree[i] === 0) queue.push(i);
}
// 4. BFS拓扑排序
let count = 0;
while (queue.length) {
const current = queue.shift();
count++;
// 遍历当前课程的后继课程
for (const next of graph[current]) {
inDegree[next]--; // 解除依赖关系
if (inDegree[next] === 0) { // 若无依赖则加入队列
queue.push(next);
}
}
}
// 5. 验证是否遍历所有课程
return count === numCourses;
};

实际应用场景#

  1. 课程安排系统:大学课程依赖关系校验`
  2. 任务调度:有依赖关系的任务执行顺序校验`
  3. 软件包管理:检测软件包依赖冲突(循环依赖)`
  4. 工作流引擎:验证工作流节点的依赖合理性`

相似题目#

课程表
https://website-truelovings-projects.vercel.app/posts/algorithms/课程表/
作者
欢迎来到StarSky的网站!
发布于
2025-08-07
许可协议
CC BY-NC-SA 4.0