题目:
方法一: DFS
看到依赖问题,首先想到把问题转换为有向图 !
- 利用条件构建图,课程即顶点,几个节点就有几个顶点,以个数建立List[ ]数组表示图,数组的索引就是顶点的值,数组的每个元素都是顶点对应的邻接表;
用先学习的课程pre指向后学习的课程follow来表示有向图的顺序 - main中用第一个for遍历图的每个顶点,对顶点的邻接表使用dfs
- 在dfs中用第二个for遍历该顶点的邻接表
当onpath为真则标记isCycle为true
注意for结束后要还原 onpath=false
注意:
- graph[i][j ]即邻接表,i 就是课程的编号; graph[i] 就是i 课程指向的所有课;
- 为便于记忆,当onpath[s]=true 和 marked[s]=true 都直接return
- onPath[s]在marked[s]之前 !
- new泛型数组后面不需要再加 <>:
List<Integer>[] graph=new List<Integer>[3];
X
List<Integer>[] graph=new List[3];
√
Java实现:
class Solution {
boolean[] marked;
boolean[] onPath;
boolean isCycle=false;
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 不可能完成: 即存在循环依赖,互相为前提, 就不能完成课程
//构建图
List<Integer>[] graph=buildGraph(numCourses,prerequisites);
//初始化
marked=new boolean[numCourses];
onPath=new boolean[numCourses];
//用dfs遍历每个顶点
for(int i=0;i<numCourses;i++){
dfs(graph,i);
}
return !isCycle;//不成环即可以完成学习
}
List<Integer>[] buildGraph(int numCourses,int[][]prerequisites){
List<Integer>[] graph=new List[numCourses];//new泛型数组后面不需要再加 <>
//初始化每个元素:new一个List作为邻接表
for(int i=0;i<numCourses;i++){
graph[i]=new LinkedList<>();
}
//添加有向边
for(int[] k:prerequisites){
int pre=k[1];
int follow=k[0];
graph[pre].add(follow); // graph的索引对应顶点的值,
}
return graph;
}
}
void dfs(List<Integer>[] graph,int s){
//终止条件 onPath[s]在marked[s]之前 !
if(onPath[s]==true){
isCycle=true;
return;
}
if(marked[s]==true){
return;
}
marked[s]=true;//标记当前s
onPath[s]=true;//标记onpath
//遍历邻接表
for(int k:graph[s]){
dfs(graph,k);
}
onPath[s]=false;//onpath还原
}
或者将判断marked和onPath放到遍历邻接表的for中
void dfs(List<Integer>[] graph,int s){
//终止条件 onPath[s]在marked[s]之前 !
marked[s]=true;//标记当前s
onPath[s]=true;//标记onpath
//遍历邻接表
for(int k:graph[s]){
if(!marked[k]){
dfs(graph,k);
}
if(onPath[k]){
isCycle=true;
return;
}
}
onPath[s]=false;//onpath还原
}
方法二:BFS
图的BFS需要借助入度数组 indegree , 用入读数组实现了marked[ ]的作用,只有入度为 0 的节点才能入队,从而保证不会出现死循环
当元素入度为0才能进入nodes队列;
开始执行 BFS 循环,不断弹出队列中的节点,减少相邻节点的入度,并将入度变为 0 的节点加入队列;
while内一个for就是一层;
图的BFS在for外面弹出!而二叉树的BFS在for里面弹出node !
这里的一个for遍历的是temp的邻接表,也就是temp下面这一层,入度为0后的点再加入nodes,和二叉树BFS类似 !
如果最终所有节点都被遍历过(count 等于节点数),则说明不存在环,反之则说明存在环
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<Integer>[] graph=buildGraph(prerequisites,numCourses);
//入度数组
int[] indegree=new int[numCourses]; //int数组会自动初始化为0
for(int[] k:prerequisites){
int from=k[1]; //先修课程from
int to=k[0]; // 后续课程to即被指的元素,from指向to,to被指的次数即入度
indegree[to]++; //入度数量++
}
//如果入度为0,才可以作为BFS的起点 !
Queue<Integer> nodes=new LinkedList<>();
for(int i=0;i<numCourses;i++){
if(indegree[i]==0){
nodes.add(i);
}
}
int count=0;
//BFS
//start是入度为0的点,即不被依赖
//没有额外的target,nodes遍历完则结束
while(!nodes.isEmpty()){
int temp=nodes.poll(); // 1. 弹出节点
count++;
for(int k :graph[temp]){ // 遍历temp的邻接表的每个元素,相当于遍历temp节点下面这一层 !
indegree[k]--;
if(indegree[k]==0){ //2. 当邻接表中有元素入度为0了,添加节点
nodes.add(k);
}
}
}
return count==numCourses; //如果所有节点都被遍历了一次则不成环
}
List<Integer>[] buildGraph(int[][] prerequisites,int numCourses){
List<Integer>[] graph=new List[numCourses];
for(int i=0;i<numCourses;i++){
graph[i]=new LinkedList<Integer>();
}
for(int[] k:prerequisites){
int pre=k[1];
int follow=k[0];
graph[pre].add(follow);
}
return graph;
}
}