題目: 課程表,有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 }