package graph; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * 题目: 207. 课程表 (canFinish) * 描述:你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 * 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。 * 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。 * 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。 * 链接:https://leetcode.cn/problems/course-schedule/ */ //思路知道,代码不好写 /* 思路解析 构建图与入度数组 用一个邻接表 graph 表示课程之间的依赖关系。 对于先修课程对 [ai, bi],表示修课程 ai 之前必须完成课程 bi,因此建立一条从 bi 到 ai 的边,并将 ai 的入度加 1。 初始化队列 将所有入度为 0 的课程(即没有先修要求的课程)加入队列,作为拓扑排序的起点。 拓扑排序 从队列中取出一个课程,将其计入已完成课程数量 count。 遍历该课程的所有后续课程,将这些课程的入度减 1。如果某个课程的入度减为 0,则说明它所有的先修课程都已完成,将其加入队列。 判断是否存在环 如果最终能处理的课程数量等于总课程数,则说明不存在环路,所有课程都可以按要求完成;否则说明存在循环依赖,无法完成所有课程。 */ public class CanFinish { public boolean canFinish(int numCourses, int[][] prerequisites) { // 构建图:graph.get(i) 存储课程 i 的后续课程 List> graph = new ArrayList<>(); // 记录每个课程的入度(即需要先修课程的数量) int[] indegree = new int[numCourses]; // 初始化图 for (int i = 0; i < numCourses; i++) { graph.add(new ArrayList<>()); } // 构建图和入度数组 // 每个先修对 [ai, bi] 表示:要修 ai 必须先修 bi // 因此 bi 指向 ai,所以 indegree[ai]++ for (int[] pre : prerequisites) { int course = pre[0]; int preCourse = pre[1]; graph.get(preCourse).add(course); indegree[course]++; } // 使用队列存储所有入度为 0 的课程 Queue queue = new LinkedList<>(); for (int i = 0; i < numCourses; i++) { if (indegree[i] == 0) { queue.offer(i); } } // 用于统计可以修的课程数 int count = 0; while (!queue.isEmpty()) { int cur = queue.poll(); count++; // 遍历当前课程的所有后续课程,将它们的入度减 1 for (int next : graph.get(cur)) { indegree[next]--; // 如果入度减到 0,说明该课程的先修都已完成 if (indegree[next] == 0) { queue.offer(next); } } } // 如果所有课程都能修完,则 count 应等于 numCourses return count == numCourses; } }