现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
示例 1:
输入: 2, [[1,0]]
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule-ii
本题实则上就是根据给定的二维数组建立图然后判断是否存在拓扑排序。如果图存在环,那么课程安排失败。如果安排成功,那么给出一个可行的课程顺序。
解答方案:
我们可以采用深度优先或者广度优先的方式进行搜索解答:
深度优先方式代码如下:
class Solution {
List<List<Integer>> edges;//相当于采用邻接表的方式存储有向边
int[] res;//表示的是结果数组,即安排的一种可行的顺序(拓扑排序)
//我们需要的是从开始到最后的一种可行的安排,但是采用深度优先的方式的话,是等待其节点回溯的时候才会加入res中
int count;
int[] vis;//表示的是节点当前的状态,0表示为访问,1表示访问中(如果对某节点设置为1以后再次出现状态为1说明产生了环
boolean existRound;//判断是否存在环的标志
public int[] findOrder(int numCourses, int[][] prerequisites) {
//采用深度优先的搜索方式
//由于n门课是从0到n-1编号,故可以采用含有n个节点的邻接表实现图的存储
//在java中用List<List<Integer>> 很容易实现邻接表
edges = new ArrayList<List<Integer>>();
for(int i = 0; i < numCourses; i++){
edges.add(new ArrayList<Integer>());
}
for(int[] row : prerequisites){
edges.get(row[1]).add(row[0]);//由于给定的[0, 1]表示的是课程1需要在课程0之前,所以需要的是1->0的边
}
count = numCourses - 1;
vis = new int[numCourses];
res = new int[numCourses];
//由于出现的图可能不是一次深度优先就可以完成的,所以需要对每个节点均进行判断是否访问过了
for(int i = 0; i < numCourses; i++){
if(vis[i] == 0 && !existRound){
dfs(i);
}
}
if(existRound) return new int[]{};
return res;
}
public void dfs(int i){//以当前i节点开始进行深度优先遍历
vis[i] = 1;//置标志为搜索中。
// for(int j = 0; j < edges.get(i).size(); j++){
// int k = edges.get(i).get(j);
// if(vis[k] == 0){
// dfs(k);
// if(existRound) return;
// }else if(vis[k] == 1){
// existRound = true;
// return;
// }
// }
//该循环与上面的作用是一样的,
for(int j : edges.get(i)){
if(vis[j] == 0){//如果i的临界点还没有访问过,则继续访问
dfs(j);
}else if(vis[j] == 1){//如果i的其中一个邻接点处于搜索中的状态,说明产生了环
existRound = true;
return ;
}
if(existRound) return;
}
vis[i] = 2;//表示i结点访问完成,设置其状态为2
res[count--] = i;//存储结果
}
}
广度优先方式的代码如下:
class Solution {
List<List<Integer>> edges;//相当于采用邻接表的方式存储有向边
int[] res;//表示的是结果数组,即安排的一种可行的顺序(拓扑排序),相对于深度优先,广度优先更加的直观,其解决方法是正向的
int[] indulg;//存储节点的入度
public int[] findOrder(int numCourses, int[][] prerequisites) {
//采用广度优先的搜索方式,该方式需要存储每个节点的入度
//由于n门课是从0到n-1编号,故可以采用含有n个节点的邻接表实现图的存储
//在java中用List<List<Integer>> 很容易实现邻接表
edges = new ArrayList<List<Integer>>();
for(int i = 0; i < numCourses; i++){
edges.add(new ArrayList<Integer>());
}
indulg = new int[numCourses];
for(int[] row : prerequisites){
edges.get(row[1]).add(row[0]);//由于给定的[0, 1]表示的是课程1需要在课程0之前,所以需要的是1->0的边
++indulg[row[0]];
}
res = new int[numCourses];
Queue<Integer> qu = new LinkedList<Integer>();
//由于出现的图可能不是一次广度优先就可以完成的,所以需要对每个入度为0的节点入队
for(int i = 0; i < numCourses; i++){
if(indulg[i] == 0){
qu.offer(i);
}
}
int count = 0;
while(!qu.isEmpty()){//队列中的节点全部是入度为0的节点
int u = qu.poll();
res[count++] = u;//u便是当前可以安排的课程
for(int v : edges.get(u)){
--indulg[v]; //这里需要先把入度减1,因为从u到v的边的入度计算需要减去
if(indulg[v] == 0){//满足入队的条件
qu.offer(v);
}
}
}
if(count != numCourses) return new int[]{};
return res;
}
}