题目:你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
-
例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
思想:本题的课程可以看成图中的一个个节点,这道题就换成了对图的经典的拓扑排序题目,将每一门课看成一个节点,如果学习课程1
之前必须学习课程2
,则拓扑排序中,2
一定出现在1
之前,若存在环形就说明不可能存在拓扑结构
-
使用深度优先搜索,用栈存储搜索完成的节点
-
对任意一个节点而言,如果它的有向边(比如学习课程
1
之前必须学习课程2
,那么课程2
这个节点就必须先入栈,最后判断是否存在这样一个入栈的结果,存在就说明这样的课程有效)的相邻节点(比如课程2
)搜索完成,并入栈后,再将该节点(比如课程1
)入栈即可 -
最终从栈顶到栈底的顺序就是一种可行的拓扑排序
-
对于节点来说有三种状态:
-
未搜索:还没有搜索到这个节点
-
搜索中:即还有相邻节点正在搜索
-
已完成:该节点的相邻节点以及入栈,该节点也已经入栈,保证该节点的顺序永远在相邻节点后入栈(保证要学习课程
1
之前必须学习课程2
)
-
-
代码中实现:对当前搜索节点
u
标记为搜索中
,遍历相邻节点v
-
如果
v
为未搜索
:开始搜索节点v
-
如果
v
为搜索中
:说明存在一个环,那么就不存在一个拓扑排序(已经对节点u
标记为搜索中
,搜索的是相邻节点,那么相邻节点就不可能出现搜索中
,除非是环,即u
指向v
,同时v
指向了u
) -
如果
v
为已完成
:说明v
已经入栈,u
还没有入栈 -
当
u
的所有相邻节点已经标记为已完成
,说明搜索结束,将u
也放入栈中,并标记为已完成
-
总结:这道题的难点在于将课程的学习顺序和拓扑排序联想到一起,因此需要具有发散性思维和扎实的数据结构基础
代码:
class Solution {
//创建一个二维集合,存储每一个节点的后继结点([B,A]:先修课程A,然后才能修课程B,则B是A的后继结点)
List<List<Integer>> succNode;
//创建一个标记位数组,用来确定每个节点是否被搜索过:
//0:未搜索
//1: 搜索中;此时为环(已经对节点`u`标记为`搜索中`,搜索的是相邻节点,那么相邻节点就不可能出现`搜索中`,除非是环)
//2: 搜索完成
int[] flag;
//创建一个状态变量,表示是否存在符合条件的拓扑排序
boolean res = true;
public boolean canFinish(int numCourses, int[][] prerequisites) {
//初始化二维集合
succNode = new ArrayList<>();
for(int i = 0; i < numCourses; i++){
succNode.add(new ArrayList<Integer>());
}
//将每一个节点的后继节点存入二维集合
for(int[] info : prerequisites){
//[A,B]:A是B的后继结点;所以存入集合时,应该是info[1]代表B这个节点的后继结点为info[0]代表A
succNode.get(info[1]).add(info[0]);
}
//标记为一开始都设置为0:表示未搜索
flag = new int[numCourses];
//对未搜索的节点进行DFS搜索
for(int i = 0; i < numCourses; i++){
if(flag[i] == 0){
dfs(i);
}
}
return res;
}
//对节点 u 进行dfs搜索
private void dfs(int u){
//开始搜索时标记位改为1
flag[u] = 1;
//搜索该节点的所有未搜索过的后继结点
//注意:for-each结构具有自动处理 空内容 的情况,不会抛出异常,而是直接跳过代码块的执行
for(int v : succNode.get(u)){
if(flag[v] == 0){
dfs(v);
//如果某一次搜索后res为false,说明搜索过程中存在环,直接返回false即可
if(!res){
return;
}
}//如果flag[v] == 1;说明存在环,直接返回false
else if(flag[v] == 1){
res = false;
return;
}
}
//每一个节点的所有未搜索过的后继结点搜索完成需要标记为2;表示已完成
flag[u] = 2;
}
}