207. Course Schedule
题目:
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