zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    BZOJ1042: [HAOI2008]硬币购物
    BZOJ1089: [SCOI2003]严格n元树
    BZOJ1060: [ZJOI2007]时态同步
    BZOJ2697: 特技飞行
    BZOJ2464: 中山市选[2009]小明的游戏
    BZOJ1430: 小猴打架
    BZOJ3675: [Apio2014]序列分割
    BZOJ2453: 维护队列
    BZOJ2120: 数颜色
    BZOJ4547: Hdu5171 小奇的集合
  • 原文地址:https://www.cnblogs.com/yrbbest/p/4493547.html
Copyright © 2011-2022 走看看