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.

click to show more hints.

Hints:
    1. 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.
    2. Topological Sort via DFS - A great video tutorial (21 minutes) on Coursera explaining the basic concepts of Topological Sort.
    3. 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算法有两种

  1. 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大学的课件。
  2. 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

posted @ 2015-05-11 03:53  YRB  阅读(4712)  评论(0编辑  收藏  举报