0x01.问题
现在你总共有 n 门课需要选,记为 0
到 n-1
。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
。
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
示例 1:
输入: 2, [[1,0]] 输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。
因此,正确的课程顺序为
[0,1] 。示例 2:
输入: 4, [[1,0],[2,0],[3,1],[3,2]] 输出: [0,1,2,3] or [0,2,1,3] 解释: 总共有 4
门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
说明:
- 输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
- 你可以假定输入的先决条件中没有重复的边。
提示:
- 这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
- 通过 DFS 进行拓扑排序 -
- 拓扑排序也可以通过 BFS 完成。
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/course-schedule-ii
0x02.一步步分析思路
-
读题,获取一些有效信息:
- 围绕的是
0
到n-1
的这n
门课程的选取顺序问题。 - 第二个参数代表着这些课程之间的依赖关系,即
[0,1]
表示要学0,先学1。 - 结果需要返回选取课程的顺序,如果不能全部选取,返回空数组。
- 围绕的是
-
一步步来解决问题,第一步:什么样的课程才能被依次选取?
- 换句话说,在没有选任何课程的时候,我们能够选什么课,在选了一些课程之后,又能够选什么课?
- 在没有选任何课时,很简单,所有不依赖其它课的课都可以选,这在这个依赖关系图中也就是入度为0的情况。
- 在选了一些课程的情况下,有些复杂了,但总体还是:可以选依赖已经选过的课的课程。
- 综合一下上述两种情况,我们发现,似乎总在提醒着我们什么,综合两种情况,最重要的就是一个节点的入度情况,也就是说,入度为0就可以选了,依赖的课每选一门,则相应的入度减1,这两种情况也就能统一化了。
- 所以,第一步就是要维护一个存储节点入度情况的数组
courseLib
.
-
第二步,如何继续对第一步的想法继续完善?
- 维护了一个存储入度的数组后,入度的数量有了,但实际上每门课的依赖都是具体的,所以我们肯定需要一个具体的
List
集合来存储每个节点,也就是每门课程的具体依赖情况。 - 接下来的工作就好办了,先扫描一遍原依赖关系,维护好
List
和courseLib
,然后把入度为0的节点存入队列,进行广度优先搜索(在这其实也就是拓扑排序的思想),在搜索的过程中,不断的把入度为0的课程加入结果数组中,并更新相应的courseLib
。
- 维护了一个存储入度的数组后,入度的数量有了,但实际上每门课的依赖都是具体的,所以我们肯定需要一个具体的
-
第三步,怎样知道是否能够选完全部课程?
- 这个就比较简单了,只要最后结果数组的不小不等于课程数量,就说明不能选完全部课程。
-
到这里,分析基本结束了,接下来就是具体的算法思路了:
- (1)维护
lists
存储每个节点可到达的节点集合。
List<Integer>[] lists=new ArrayList[numCourses];
- (2)维护
courseLib
存储每个节点的入度。
int[] courseLib=new int[numCourses];
- (3)扫描输入的图,更新
lists
和courseLib
。 - (4)获取入度为0的节点,加入队列
- (5)BFS:将入度为0的直接加入结果数组中,并更新,对节点的入度减1,将更新后入度为0的节点继续加入队列。
- (5)如果结果数组大小等于课程数,就返回结果数组,否则返回空数组。
- (1)维护
0x03.解决代码–(拓扑排序?:BFS)
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
//存储每个节点可到达的节点集合
List<Integer>[] lists=new ArrayList[numCourses];
//存储每个节点的入度
int[] courseLib=new int[numCourses];
//最终答案
int[] result=new int[numCourses];
//控制结果数组的下标
int index=0;
//扫描输入的图
for(int[] p:prerequisites){
courseLib[p[0]]++;
if(lists[p[1]]==null){
lists[p[1]]=new ArrayList<>();
}
lists[p[1]].add(p[0]);
}
Queue<Integer> queue=new LinkedList<>();
//获取入度为0的节点,加入队列
for(int i=0;i<numCourses;i++){
if(courseLib[i]==0){
queue.add(i);
}
}
while(!queue.isEmpty()){
int size=queue.size();
while(size-->0){
int curr=queue.poll();
//入度为0的直接加入结果数组中
result[index++]=curr;
List<Integer> list=lists[curr];
if(list==null){
continue;
}
//更新,对节点的入度减1
for(int val:list){
courseLib[val]--;
if(courseLib[val]==0){
queue.add(val);
}
}
}
}
return index==numCourses?result:new int[0];
}
}
ATFWUS --Writing By 2020–05-17