第一次看“课程表”这题,很多人会被题面绕一下:[a, b] 表示想学 a,得先学 b。这句话翻成代码,其实就是一条有向边 b -> a。
题目问的是:能不能把 numCourses 门课都学完。再直白一点,就是这张图里有没有环。 有环就没法排完,比如:
// 0 依赖 1,1 又依赖 0prerequisites = vec![vec![0, 1], vec![1, 0]];
这种关系一出来,谁都上不了。
这题我更喜欢用 拓扑排序 写,不是因为它高级,而是因为代码顺手,也好解释。核心就两步:
先统计每门课的入度,也就是“还有几门前置课没修完”; 再把所有入度为 0 的课程丢进队列,一门门弹出来,顺手把它指向的后续课程入度减 1。
如果最后处理掉的课程数量等于总课程数,说明没有环;反过来,说明还剩一坨课程互相卡着。
直接看 Rust 写法:
use std::collections::VecDeque;impl Solution {pubfncan_finish(num_courses: i32, prerequisites: Vec<Vec<i32>>) -> bool {let n = num_courses asusize;letmut graph = vec![Vec::new(); n];letmut indegree = vec![0; n];for edge in prerequisites {let a = edge[0] asusize;let b = edge[1] asusize; graph[b].push(a); indegree[a] += 1; }letmut queue = VecDeque::new();for i in0..n {if indegree[i] == 0 { queue.push_back(i); } }letmut taken = 0;whileletSome(course) = queue.pop_front() { taken += 1;for &next in &graph[course] { indegree[next] -= 1;if indegree[next] == 0 { queue.push_back(next); } } } taken == n }}
这段代码里真正要盯住的是这两行:
graph[b].push(a);indegree[a] += 1;
不少人会写反,写成 a -> b,然后结果一直不对。因为题目给的是“学 a 之前先学 b”,所以边一定是从前置课指向当前课。
再拿一组数据过一下:
num_courses = 4prerequisites = vec![vec![1, 0],vec![2, 0],vec![3, 1],vec![3, 2],];
这里 0 可以先学,学完以后 1 和 2 的入度都能减到 0,最后再学 3。整个过程能走通,所以返回 true。
这题时间复杂度不高,O(V + E),课程和依赖各扫一遍。面试里如果你能把“入度、队列、处理数”这几个点讲清楚,基本就够了。
真做题的时候,不用一上来就念“这是经典有向图判环问题”。先把 b -> a 这个关系落对,再把入度表维护对,代码自然就出来了。