题目链接

LeetCode 课程表I
LeetCode 课程表II

题目描述

补充题:
现有n个编译项,编号为0 ~ n-1。给定一个二维数组,表示编译项之间有依赖关系。如[0, 1]表示1依赖于0。
若存在循环依赖则返回空;不存在依赖则返回可行的编译顺序。
现在你总共有 numCourses 门课需要选,记为 0numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai必须 先选修 bi

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1]

返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组

示例 1:

输入: numCourses = 2, prerequisites = [[1,0]]
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:

输入: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出: [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]

示例 3:

输入: numCourses = 1, prerequisites = []
输出: [0]

提示:

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= numCourses * (numCourses - 1)
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • ai != bi
  • 所有[ai, bi] 匹配 互不相同

拓展:

  • 这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
  • 通过 DFS 进行拓扑排序 - 一个关于 Coursera 的精彩视频教程(21 分钟),介绍拓扑排序的基本概念。
  • 拓扑排序也可以通过 BFS 完成。

    解题思路

    方法一:拓扑排列(BFS法)

    拓扑排序算法过程:
  1. 选择图中一个入度为0的点,记录下来
  2. 在图中删除该点和所有以它为起点的边
  3. 重复1和2,直到图为空或没有入度为0的点。

算法流程
1、在开始排序前,扫描对应的存储空间(使用邻接表),将入度为 0 的结点放入队列。
2、只要队列非空,就从队首取出入度为 0 的结点,将这个结点输出到结果集中,并且将这个结点的所有邻接结点(它指向的结点)的入度减 1,在减 1 以后,如果这个被减 1 的结点的入度为 0 ,就继续入队。
3、当队列为空的时候,检查结果集中的顶点个数是否和课程数相等即可。
(思考这里为什么要使用队列?如果不用队列,还可以怎么做,会比用队列的效果差还是更好?)
在代码具体实现的时候,除了保存入度为 0 的队列,我们还需要两个辅助的数据结构:
1、邻接表:通过结点的索引,我们能够得到这个结点的后继结点;
2、入度数组:通过结点的索引,我们能够得到指向这个结点的结点个数。
这个两个数据结构在遍历题目给出的邻边以后就可以很方便地得到。

  1. class Solution {
  2. public int[] findOrder(int numCourses, int[][] prerequisites) {
  3. if(numCourses == 0){
  4. return new int [0];
  5. }
  6. // 邻接表 数组
  7. HashSet<Integer>[] adj = new HashSet[numCourses];
  8. for(int i = 0; i < numCourses; ++i){
  9. adj[i] = new HashSet<Integer>();
  10. }
  11. // 入度
  12. int[] inDegree = new int[numCourses];
  13. for(int[] p : prerequisites){
  14. // 从p[1] 到 p[0]
  15. // 所以 p[1] 节点邻接表 加 p[0] 节点
  16. adj[p[1]].add(p[0]);
  17. // p[0] 节点入度加一
  18. ++inDegree[p[0]];
  19. }
  20. // 入度为 0 的节点
  21. Queue<Integer> queue = new LinkedList<>();
  22. for(int i = 0; i < numCourses; ++i){
  23. if(inDegree[i] == 0){
  24. queue.offer(i);
  25. }
  26. }
  27. // 结果
  28. int[] res = new int[numCourses];
  29. // 当前结果集列表里的元素个数,正好可以作为下标
  30. int count = 0;
  31. while(!queue.isEmpty()){
  32. // 入度为 0 的节点
  33. Integer head = queue.poll();
  34. // 加入结果集 下标加一
  35. res[count++] = head;
  36. // 当前加入 节点 的邻接表 对应的 节点 入度都减一
  37. Set<Integer> successors = adj[head];
  38. for(Integer nextCourse : successors){
  39. --inDegree[nextCourse];
  40. if(inDegree[nextCourse] == 0){
  41. queue.offer(nextCourse);
  42. }
  43. }
  44. }
  45. if(count == numCourses){
  46. return res;
  47. }
  48. return new int[0];
  49. }
  50. }
class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] indegree = new int[numCourses];
        List<List<Integer>> adj = new ArrayList<List<Integer>>();
        Queue<Integer> queue = new LinkedList<Integer>();
        int[] res = new int[numCourses];
        int pos = 0;
        Arrays.fill(res, -1); 
        for(int i = 0; i < numCourses; ++i){
            adj.add(new ArrayList<Integer>());
        }
        for(int[] cp : prerequisites){
            indegree[cp[0]]++;
            adj.get(cp[0]).add(cp[1]);
        }
        for(int i = 0; i < numCourses; ++i){
            if(indegree[i] == 0){
                queue.offer(i);

            }
        }
        while(!queue.isEmpty()){
            int k = queue.poll();
            res[pos++] = k;
            for(int i = 0; i < numCourses; ++i){
                if(adj.get(i).contains(k)){
                    indegree[i]--;
                    if(indegree[i] == 0){
                        queue.offer(i);
                    }
                }
            }
        }
        for(int val : indegree){
            if(val != 0){
                return new int[0];
            }
        }
        return res;
    }
}
  • 时间复杂度 O(E+V) E为临边的个数, V为点的个数
  • 空间复杂度 O(V)

如果是请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
则直接判断结果集元素个数是否等于课程个数 不用记录结果

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        if(numCourses < 2){
            return true;
        }
        HashSet<Integer>[] adj = new HashSet[numCourses];
        for(int i = 0; i < numCourses; ++i){
            adj[i] = new HashSet<Integer>();
        }
        int[] indegree = new int[numCourses];
        for(int[] p : prerequisites){
            adj[p[1]].add(p[0]);
            ++indegree[p[0]];
        }
        Queue<Integer> queue = new LinkedList<>();
        for(int i = 0; i < numCourses; ++i){
            if(indegree[i] == 0){
                queue.offer(i);
            }
        }
        int count = 0;
        while(!queue.isEmpty()){
            int head = queue.poll();
            count++;
            Set<Integer> set = adj[head];
            for(Integer s : set){
                --indegree[s];
                if(indegree[s] == 0){
                    queue.offer(s);
                }
            }
        }
        if(count == numCourses){
            return true;
        }
        return false;
    }
}
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] indegree = new int[numCourses];
        List<List<Integer>> adj = new ArrayList<List<Integer>>();
        Queue<Integer> queue = new LinkedList<>();
        // 保存入度的课程号
        for(int i = 0; i < numCourses; ++i){
            adj.add(new ArrayList<Integer>());
        }
        for(int[] cp : prerequisites){
            adj.get(cp[1]).add(cp[0]);
            indegree[cp[1]]++;
        }
        for(int i = 0; i < numCourses; ++i){
            if(indegree[i] == 0){
                queue.offer(i);
            }
        }
        while(!queue.isEmpty()){
            int k = queue.poll();
            for(int i = 0; i < numCourses; ++i){
                if(adj.get(i).contains(k)){
                    indegree[i]--;
                    if(indegree[i] == 0){
                        queue.offer(i);
                    }
                }                
            }
        }
        for(int i : indegree){
            if(i != 0){
                return false;
            }
        }
        return true;
    }
}

返回顺序:

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        Queue<Integer> q = new LinkedList<Integer>();
        int[] indegree = new int[numCourses];
        List<Integer> res = new LinkedList<>();
        List<List<Integer>> adj = new ArrayList<List<Integer>>();
        for(int i = 0; i < numCourses; ++i){
            adj.add(new ArrayList<Integer>());
        }
        for(int[] prerequisite : prerequisites){
            ++indegree[prerequisite[1]];
            adj.get(prerequisite[1]).add(prerequisite[0]);
        }
        for(int i = 0; i < numCourses; ++i){
            if(indegree[i] == 0){
                q.offer(i);
            }
        }
        while(!q.isEmpty()){
            int k = q.poll();
            res.add(0, k);
            for(int i = 0; i < numCourses; ++i){
                if(adj.get(i).contains(k)){
                    --indegree[i];
                    if(indegree[i] == 0){
                        q.offer(i);
                    }
                }
            }
        }
        if(res.size() != numCourses){
            return new int[]{};
        }
        int[] ans = new int[res.size()];
        for(int i = 0; i < res.size(); ++i){
            ans[i] = res.get(i);
        }
        return ans;

    }
}
  • 时间复杂度 O(E+V) E为临边的个数, V为点的个数
  • 空间复杂度 O(V)