🚩传送门:力扣题目

题目

你这个学期必须选修 [LC]210. 课程表 II - 图1 门课程,记为 [LC]210. 课程表 II - 图2[LC]210. 课程表 II - 图3

在选修某些课程之前需要一些先修课程。 先修课程按数组 [LC]210. 课程表 II - 图4给出,其中 [LC]210. 课程表 II - 图5 ,表示如果要学习课程 [LC]210. 课程表 II - 图6 必须 先学习课程 [LC]210. 课程表 II - 图7。例如,先修课程对[LC]210. 课程表 II - 图8表示:想要学习课程 [LC]210. 课程表 II - 图9 ,你需要先完成课程 [LC]210. 课程表 II - 图10

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

示例 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]

解题思路:拓扑排序

拓扑排序可以深搜(DFS)也可以广搜(BFS)

本题是一道经典的「拓扑排序」问题,给定一个包含 [LC]210. 课程表 II - 图11 个节点的有向图 [LC]210. 课程表 II - 图12,我们给出它的节点编号的一种排列。
如果满足:对于图[LC]210. 课程表 II - 图13中的任意一条有向边[LC]210. 课程表 II - 图14[LC]210. 课程表 II - 图15 在排列中都出现在 [LC]210. 课程表 II - 图16 的前面。那么称该排列是图 [LC]210. 课程表 II - 图17的「拓扑排序」。

根据上述的 定义,我们可以得出两个结论:

  • 如果图[LC]210. 课程表 II - 图18中存在环(即图[LC]210. 课程表 II - 图19不是「有向无环图」),那么图[LC]210. 课程表 II - 图20不存在拓扑排序

    这是因为假设图中存在环 [LC]210. 课程表 II - 图21,那么 [LC]210. 课程表 II - 图22在排列中必须出现在 [LC]210. 课程表 II - 图23 的前面,但 [LC]210. 课程表 II - 图24 同时也必须出现在[LC]210. 课程表 II - 图25 的前面,因此无法满足这个要求

  • 如果图[LC]210. 课程表 II - 图26是有向无环图,那么它的拓扑排序可能不止一种。

    举一个最极端的例子,如果图[LC]210. 课程表 II - 图27值包含[LC]210. 课程表 II - 图28 个节点却没有任何边,那么任意一种编号的排列都可以作为拓扑排序。

广度搜索
[LC]210. 课程表 II - 图29

复杂度分析

时间复杂度:[LC]210. 课程表 II - 图30,遍历一个图需要访问所有节点和所有临边, [LC]210. 课程表 II - 图31[LC]210. 课程表 II - 图32 分别为节点数量和临边数量;

空间复杂度:[LC]210. 课程表 II - 图33,为建立邻接表所需额外空间,[LC]210. 课程表 II - 图34 长度为 [LC]210. 课程表 II - 图35 ,并存储 [LC]210. 课程表 II - 图36 条临边的数据。

我的代码

  1. class Solution {
  2. public int[] findOrder(int numCourses, int[][] prerequisites) {
  3. List<List<Integer>>edges=new ArrayList<>();
  4. int[]inedges=new int[numCourses];
  5. for (int i = 0; i < numCourses; i++) {
  6. edges.add(new ArrayList<>());
  7. }
  8. // 边和入度
  9. for (int i = 0; i < prerequisites.length; i++) {
  10. inedges[prerequisites[i][0]]++;
  11. edges.get(prerequisites[i][1]).add(prerequisites[i][0]);
  12. }
  13. LinkedList<Integer>queue=new LinkedList<>();
  14. // 入度为0的结点u入队
  15. for (int i = 0; i < numCourses; i++) {
  16. if(inedges[i]==0) queue.addLast(i);
  17. }
  18. ArrayList<Integer>res=new ArrayList<>();
  19. while(!queue.isEmpty()){
  20. int u=queue.pollFirst();
  21. res.add(u); // 先学习u
  22. numCourses--;
  23. for (int i = 0; i < edges.get(u).size(); i++) {
  24. int v=edges.get(u).get(i); // 得到结点v
  25. inedges[v]--; //入度减少
  26. if(inedges[v]==0)queue.addLast(v);
  27. }
  28. }
  29. if(numCourses!=0) res.clear(); // 有环需要返回空数组
  30. return res.stream().mapToInt(Integer::intValue).toArray();
  31. }
  32. }

深度优先搜索
[LC]210. 课程表 II - 图37

复杂度分析

时间复杂度:[LC]210. 课程表 II - 图38,遍历一个图需要访问所有节点和所有临边, [LC]210. 课程表 II - 图39[LC]210. 课程表 II - 图40 分别为节点数量和临边数量;

空间复杂度:[LC]210. 课程表 II - 图41,为建立邻接表所需额外空间,[LC]210. 课程表 II - 图42 长度为 [LC]210. 课程表 II - 图43 ,并存储 [LC]210. 课程表 II - 图44 条临边的数据。

我的代码

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. class Solution {
  4. boolean valid = true;
  5. int index;
  6. public int[] findOrder(int numCourses, int[][] prerequisites) {
  7. index = numCourses - 1;
  8. List<List<Integer>> edges = new ArrayList<List<Integer>>();
  9. int[] visited = new int[numCourses];
  10. int[] res = new int[numCourses];
  11. for (int i = 0; i < numCourses; ++i) {
  12. edges.add(new ArrayList<Integer>());
  13. }
  14. // 添加边的信息
  15. for (int[] info : prerequisites) {
  16. edges.get(info[1]).add(info[0]);
  17. }
  18. // 遍历每一个结点做起始结点开始dfs
  19. for (int i = 0; i < numCourses && valid; ++i) {
  20. if (visited[i] == 0) {
  21. dfs(i, visited, edges, res);
  22. }
  23. }
  24. if (!valid) return new int[0];
  25. return res;
  26. }
  27. public void dfs(int u, int[] visited, List<List<Integer>> edges, int[] res) {
  28. // 正在访问
  29. visited[u] = 1;
  30. // 一条由点 u -> v 的边
  31. for (int v : edges.get(u)) {
  32. // 未访问
  33. if (visited[v] == 0) {
  34. dfs(v, visited, edges, res);
  35. if (!valid) {
  36. return;
  37. }
  38. } else if (visited[v] == 1) {
  39. // 遇到一条正在访问的边,说明有环,刚才遍历过它
  40. valid = false;
  41. return;
  42. }
  43. }
  44. // 完成访问
  45. visited[u] = 2;
  46. res[index--] = u; // 【重点】:递归最底层完成后添加进答案,倒序添加
  47. }
  48. }