LeetCode 207. 课程表
一、题目详情
原题链接:207. 课程表
你这个学期必须选修 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]
中的所有课程对 互不相同
二、深度优先遍历(DFS)
本题可以理解为 课程安排图是否是有向无环图(DAG),即课程之间规定了前置条件,不能出现环路,否则前置条件不成立。
本题可以使用深度优先遍历(DFS)解决。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
//list作为邻接表,如value = list.get(index),其中index表示value的前置课程
List<List<Integer>> list = new ArrayList<>();
/*
借助一个标志列表 flags,用于判断每个节点 i (课程)的状态:
未被 DFS 访问:i == 0;
已被其他节点启动的 DFS 访问:i == -1;
已被当前节点启动的 DFS 访问:i == 1
*/
int[] flags = new int[numCourses];
for(int i = 0; i < numCourses; i++){
//创建numCourses个ArrayList,充当邻接表
list.add(new ArrayList<>());
}
for(int[] cp : prerequisites){
//填充邻接表,其中cp[1]为前置课程,在list中体现为子list的index,cp[0]为后置课程,在list中体现为子list的value
list.get(cp[1]).add(cp[0]);
}
for(int i = 0; i < numCourses; i++){
if(!dfs(list, flags, i)){
//判断每个节点起步 DFS 是否存在环,若存在环直接返回 False
return false;
}
}
//未发现环则返回 true
return true;
}
private boolean dfs(List<List<Integer>> list, int[] flags, int i) {
if(flags[i] == 1) {
//当 flag[i] == 1,说明在本轮 DFS 搜索中节点 i 被第 2 次访问,即课程安排图有环 ,直接返回 False
return false;
}
if(flags[i] == -1){
//当 flag[i] == -1,说明当前访问节点已被其他节点启动的 DFS 访问,无需再重复搜索,直接返回 True
return true;
}
//将当前访问节点 i 对应 flag[i] 置 1,即标记其被本轮 DFS 访问过
flags[i] = 1;
for(Integer j : list.get(i)){
//递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 False
if(!dfs(list, flags, j)){
return false;
}
}
//当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 -1 并返回 True
flags[i] = -1;
return true;
}
}