題目:
There are a total of n courses you have to take, labeled from 0
to n - 1
.
Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]
Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?
For example:
2, [[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.
Note:
The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
- This problem is equivalent to finding if a cycle exists in a directed graph. If a cycle exists, no topological ordering exists and therefore it will be impossible to take all courses.
- Topological Sort via DFS - A great video tutorial (21 minutes) on Coursera explaining the basic concepts of Topological Sort.
- Topological sort could also be done via BFS.
鏈接: http://leetcode.com/problems/course-schedule/
題解:
求Course Schedule,等同問題是有向圖檢測環,vertex是course, edge是prerequisite。我覺得一般會使用Topological Sorting拓撲排序來檢測。一個有向圖假如有環則不存在Topological Order。一個DAG的Topological Order可以有大於1種。 常用的Topological Sorting算法有兩種
- Kahn's Algorithms (wiki): BFS based, start from with vertices with 0 incoming edge,insert them into list S,at the same time we remove all their outgoing edges,after that find new vertices with 0 incoming edges and go on. 詳細過程見Reference里Brown大學的課件。
- Tarjan's Algorithms (wiki): DFS based, loop through each node of the graph in an arbitrary order,initiating a depth-first search that terminates when it hits any node that has already been visited since the beginning of the topological sort or the node has no outgoing edges (i.e. a leaf node). 詳細過程見Reference里 NYU的課件。
知道了理論,下面就是coding了,早就發現自己的coding能力確實比較弱,即使明白算法也很難迅速寫出正確的程序。多練習,一點點進步吧。互換edge[0]和edge[1]也能檢測環,不過輸出的就是逆序的Tolopolical order了。
Kahn's Algorithms:
Time Complexity - O(VE), Space Complexity - O(V)。 這里需要再研究一下怎么做到O(V + E)。
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { // Kahn's Algorithm if(numCourses <= 0) return false; if(prerequisites == null || prerequisites.length == 0) return true; int[] inDegree = new int[numCourses]; for(int[] edge : prerequisites) inDegree[edge[0]]++; List<Integer> res = new ArrayList<>(); Queue<Integer> queue = new LinkedList<>(); for(int i = 0; i < inDegree.length; i++) { if(inDegree[i] == 0) queue.offer(i); } while(!queue.isEmpty()) { int source = queue.poll(); res.add(source); for(int[] edge : prerequisites) { if(edge[1] == source) { inDegree[edge[0]]--; if(inDegree[edge[0]] == 0) queue.offer(edge[0]); } } } return res.size() == numCourses; } }
Tarjan's Algorithms:
DFS based。以任意順序遍歷圖的Vertices,假如在一次dfs中,在一條從source vertex到target vertex的edge中,發現target vertex已經在這次dfs的onStack中被mark了,那么我們設置result = false,返回。結束便利這個source vertex所有的edge之后,push這個vertex to stack,這個stack是reversePost順序的。所以最后輸出時,從棧頂一個一個pop元素組成的list就是一種topological order。
Time Complexity - O(VE),Space Complexity - O(V)。 二刷時也要好好研究能否減少復雜度。
public class Solution { private boolean[] marked; // mark visited vertex private boolean[] onStack; // mark temp visited vertex for dfs private Stack<Integer> reversePost; // store topological ordering vertex private boolean result = true; public boolean canFinish(int numCourses, int[][] prerequisites) { if(prerequisites == null || prerequisites.length < 2) return true; this.marked = new boolean[numCourses]; this.onStack = new boolean[numCourses]; this.reversePost = new Stack<>(); for(int v = 0; v < numCourses; v++) { if(!this.result) // if found cycle return false; if(!marked[v]) dfs(v, prerequisites); } return true; } private void dfs(int v, int[][] prerequisites) { onStack[v] = true; // temporarily mark this vertex = true on this dfs route marked[v] = true; // permanently mark this vertex visited for(int[] edge : prerequisites) { if(edge[1] == v) { if(!marked[edge[0]]) dfs(edge[0], prerequisites); else { if(onStack[edge[0]]) this.result = false; } } } onStack[v] = false; // back-tracking reversePost.push(v); // push vertex to reversePost stack } }
二刷:
想要快只有一個辦法,就是把輸入圖List of Edges的表達方式轉變為Adjacency Lists的表達方式,留給下一次刷了。 對圖的表達形式還需要再復習。
這回還是用的老方法。
Java:
Kahn's Method - BFS
Time Complexity - O(VE), Space Complexity - O(V)
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { // Kahn's method if (numCourses <= 0 || prerequisites == null) return false; if (prerequisites.length == 0) return true; int[] inDegree = new int[numCourses]; for (int[] prerequisite : prerequisites) inDegree[prerequisite[0]]++; Queue<Integer> q = new LinkedList<>(); for (int i = 0; i < numCourses; i++) { if (inDegree[i] == 0) q.offer(i); } List<Integer> res = new ArrayList<>(); while (!q.isEmpty()) { int num = q.poll(); res.add(num); for (int[] prerequisite : prerequisites) { if (prerequisite[1] == num) { inDegree[prerequisite[0]]--; if (inDegree[prerequisite[0]] == 0) { q.offer(prerequisite[0]); } } } } return res.size() == numCourses; } }
Tarjan's method - DFS
好慢的速度...這里其實可以不用加stack操作。加上的話,最后把stack內元素全部pop出來加入到一個list里也就是一個topological order。
Time Complexity - O(VE), Space Complexity - O(V)
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { if (numCourses <= 0 || prerequisites == null) return false; if (prerequisites.length == 0) return true; boolean[] visited = new boolean[numCourses]; boolean[] onVisiting = new boolean[numCourses]; Stack<Integer> stack = new Stack<>(); for (int i = 0; i < numCourses; i++) { if (!dfs(i, prerequisites, visited, onVisiting, stack)) return false; } return true; } private boolean dfs(int i, int[][] prerequisites, boolean[] visited, boolean[] onVisiting, Stack<Integer> stack) { if (visited[i]) return true; visited[i] = true; onVisiting[i] = true; for (int[] prerequisite : prerequisites) { if (prerequisite[0] == i) { if (onVisiting[prerequisite[1]]) return false; if (!visited[prerequisite[1]]) { if (!dfs(prerequisite[1], prerequisites, visited, onVisiting, stack)) return false; } } } onVisiting[i] = false; stack.push(i); return true; } }
沒忍住還是來做優化了...換了表示方式以后速度提高了近10倍 -___-!
Kahn's Method - BFS using Graph as Adjacency Lists
Time Complexity - O(V + E), Space Complexity - O(V)
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { // Kahn's method if (numCourses <= 0 || prerequisites == null) return false; if (prerequisites.length == 0) return true; int[] inDegree = new int[numCourses]; List<List<Integer>> graph = new ArrayList<>(); for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<Integer>()); for (int i = 0; i < prerequisites.length; i++) { inDegree[prerequisites[i][0]]++; graph.get(prerequisites[i][1]).add(prerequisites[i][0]); } Queue<Integer> q = new LinkedList<>(); for (int i = 0; i < numCourses; i++) { if (inDegree[i] == 0) q.offer(i); } List<Integer> res = new ArrayList<>(); while (!q.isEmpty()) { int num = q.poll(); res.add(num); for (int i : graph.get(num)) { inDegree[i]--; if (inDegree[i] == 0) { q.offer(i); } } } return res.size() == numCourses; } }
Tarjan's Method - DFS using Graph as Adjacency Lists
Time Complexity - O(V + E), Space Complexity - O(V)
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { if (numCourses <= 0 || prerequisites == null) return false; if (prerequisites.length == 0) return true; List<List<Integer>> graph = new ArrayList<>(); for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<Integer>()); for (int i = 0; i < prerequisites.length; i++) graph.get(prerequisites[i][1]).add(prerequisites[i][0]); boolean[] visited = new boolean[numCourses]; boolean[] onVisiting = new boolean[numCourses]; Stack<Integer> stack = new Stack<>(); for (int i = 0; i < numCourses; i++) { if (!dfs(i, graph, visited, onVisiting, stack)) return false; } return true; } private boolean dfs(int num, List<List<Integer>> graph, boolean[] visited, boolean[] onVisiting, Stack<Integer> stack) { if (visited[num]) return true; visited[num] = true; onVisiting[num] = true; for (int i : graph.get(num)) { if (onVisiting[i]) return false; if (!dfs(i, graph, visited, onVisiting, stack)) return false; } onVisiting[num] = false; stack.push(num); return true; } }
三刷:
依然使用了上面的方法。
Java:
Kahn's Algorithm - BFS
這里要使用List<List<>>來做adjacencyListsGraph,而不能用List<Set<>>來做,為什么呢?因為假如題目給出的prerequisites里面有重復case的話,我們下面的代碼在計算inDegree的時候並沒有區分,但使用List就可以避免這種情況了。速度還可以進一步優化。
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { if (numCourses < 0 || prerequisites == null) return false; if (prerequisites.length == 0) return true; List<List<Integer>> adjacencyListsGraph = new ArrayList<>(); for (int i = 0; i < numCourses; i++) adjacencyListsGraph.add(new ArrayList<>()); int[] inDegrees = new int[numCourses]; for (int[] prerequisite : prerequisites) { adjacencyListsGraph.get(prerequisite[1]).add(prerequisite[0]); inDegrees[prerequisite[0]]++; } Queue<Integer> q = new LinkedList<>(); for (int i = 0; i < numCourses; i++) { if (inDegrees[i] == 0) q.offer(i); } List<Integer> res = new ArrayList<>(); while (!q.isEmpty()) { int src = q.poll(); res.add(src); for (int dest : adjacencyListsGraph.get(src)) { inDegrees[dest]--; if (inDegrees[dest] == 0) q.offer(dest); } } return res.size() == numCourses; } }
Tarjan's Algorithms - DFS
跟二刷一樣。速度會比bfs更快。注意我們可以從任意點開始DFS.
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { if (numCourses < 0 || prerequisites == null) return false; if (prerequisites.length == 0) return true; List<List<Integer>> adjListsGraph = new ArrayList<>(); for (int i = 0; i < numCourses; i++) adjListsGraph.add(new ArrayList<>()); for (int[] prerequisite : prerequisites) adjListsGraph.get(prerequisite[1]).add(prerequisite[0]); List<Integer> res = new ArrayList<>(); boolean[] visited = new boolean[numCourses]; boolean[] onVisitingPath = new boolean[numCourses]; for (int i = 0; i < numCourses; i++) { if (!visited[i] && !canFinish(i, adjListsGraph, visited, onVisitingPath)) return false; } return true; } private boolean canFinish(int courseNum, List<List<Integer>> adjListsGraph, boolean[] visited, boolean[] onVisitingPath) { if (visited[courseNum]) return true; onVisitingPath[courseNum] = true; for (int dependent : adjListsGraph.get(courseNum)) { if (onVisitingPath[dependent] || (!visited[dependent] && !canFinish(dependent, adjListsGraph, visited, onVisitingPath))) { return false; } } onVisitingPath[courseNum] = false; visited[courseNum] = true; return true; } }
Reference:
https://en.wikipedia.org/wiki/Cycle_detection
https://en.wikipedia.org/wiki/Topological_sorting
http://cs.brown.edu/courses/cs016/lectures/14%20DAGS%20and%20Top%20Sort.pdf
http://www.cs.nyu.edu/courses/summer04/G22.1170-001/6a-Graphs-More.pdf
https://leetcode.com/discuss/76205/6ms-java-dfs-solution
http://www.geeksforgeeks.org/detect-cycle-undirected-graph/
http://www.geeksforgeeks.org/detect-cycle-in-a-graph/
http://stackoverflow.com/questions/261573/best-algorithm-for-detecting-cycles-in-a-directed-graph
https://leetcode.com/discuss/34791/bfs-topological-sort-and-dfs-finding-cycle-by-c
https://leetcode.com/discuss/39456/java-dfs-and-bfs-solution
https://leetcode.com/discuss/42543/18-22-lines-c-bfs-dfs-solutions
https://leetcode.com/discuss/35035/oo-easy-to-read-java-solution
https://leetcode.com/discuss/35578/easy-bfs-topological-sort-java
http://rosettacode.org/wiki/Topological_sort
http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
https://leetcode.com/discuss/69387/34ms-java-bfs-toposort-with-kahns-algorithm
https://leetcode.com/discuss/69396/20ms-java-dfs-toposort-with-tarjans-algorithm
http://www.1point3acres.com/bbs/thread-136257-1-1.html
https://leetcode.com/discuss/95311/5ms-98-6%25-java-dfs-topology-sort-solution