题目: 课程表,有n个课程,[0, n-1];在修一个课程前,有可能要修前导课程;
举例:
2, [[1,0]] 修课程1前需要先修课程0
There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.
2, [[1,0],[0,1]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.
解题思路:
其本质是逆拓扑排序,因此首先要将给定的课程关系转换为邻接表存储,即将给定的图转化为邻接表;
举例说明:
4, [[1,0],[2,0],[3,1],[3,2]]
1) 代码5的for循环是为了初始化一个邻接链表的结构,[[],[],[],[],]
2) 代码10的for循环是为了找到有那几个节点要依靠当前节点;其中prerequisites[0][1] = 0,prerequisites[0][0] = 1,表示课程1要依靠课程0;prerequisites[1][1] = 0,prerequisites[1][0] = 2,表示课程2依靠课程0;以此类推,则最终posts中的结果为:
[[1,2],[3],[3],[]]表示课程1,2依靠课程0,课程3依靠课程1,课程3依靠课程2,没有课程依靠课程3;
3) 代码15声明了一个数组preNums,下面代码16的for循环来告诉我们这个数组是干什么的;
4) 代码16的for循环中,拿出post中的每一个set,假设此时拿出的是post.get(0),则拿出的是依靠0课程才能修完课程的集合[1,2];该循环结束后,preNums=[0,1,1,2]表示0课程不依赖其他课程(0课程的出度为0),1课程依赖一个课程,2课程依赖一个课程,3课程依赖两个课程;因此preNums数组表示下标的课程依赖几个其他的课程
5) 到此为止,我们记录了下标课程被哪几个课程依赖,即posts;下标课程自己依赖几个课程,即preNums数组;到这里可以看出我们使用的逆拓扑结构的思想;即找出preNums数组中为0的那个下标课程(在图中表示出度为0),代码27行的for循环就是干这个事情的;
6) 假设此时没有找到出度为0的那个课程,则表示课程中有相互依赖的情况,则直接return false; 如果找到出度为0的那个课程,在此例子中为课程0,而此时课程0在post中可以得出其被[1,2]课程依赖,因此需要删除0节点,那么相应的也要删掉课程1和课程2分别对课程0的依赖,那此时,需要更新preNums数组,即1课程所依赖的课程数减一,
2课程所依赖的课程数减一,则此时preNums = [-1, 0, 0, 2],-1表示已经删除了0节点。循环5,6)则可以判断中课程是否可以顺利修完,即给出的数组是否是一个逆拓扑结构;
1 public class Solution { 2 public boolean canFinish(int numCourses, int[][] prerequisites) { 3 // init the adjacency list 4 List<Set> posts = new ArrayList<Set>(); 5 for (int i = 0; i < numCourses; i++) { 6 posts.add(new HashSet<Integer>()); 7 } 8 9 // fill the adjacency list,找到有哪几个点要依靠该点 10 for (int i = 0; i < prerequisites.length; i++) { 11 posts.get(prerequisites[i][1]).add(prerequisites[i][0]); 12 } 13 14 // count the pre-courses 15 int[] preNums = new int[numCourses]; // 计算下标的课程依赖几个课程 16 for (int i = 0; i < numCourses; i++) { 17 Set set = posts.get(i); 18 Iterator<Integer> it = set.iterator(); 19 while (it.hasNext()) { 20 preNums[it.next()]++; 21 } 22 } 23 // remove a non-pre course each time 24 for (int i = 0; i < numCourses; i++) { 25 // find a non-pre course 26 int j = 0; 27 for ( ; j < numCourses; j++) { // 找到出度为0的点,删掉,并更新依靠该点的前驱点的个数 28 if (preNums[j] == 0) break; 29 } 30 31 // if not find a non-pre course 32 if (j == numCourses) return false; 33 34 preNums[j] = -1; 35 36 // decrease courses that post the course 37 Set set = posts.get(j); 38 Iterator<Integer> it = set.iterator(); 39 while (it.hasNext()) { 40 preNums[it.next()]--; // 删除依赖j节点的节点的出度 41 } 42 } 43 44 return true; 45 } 46 }