🚩传送门:力扣题目
题目
你这个学期必须选修 门课程,记为
到
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 给出,其中
,表示如果要学习课程
则 必须 先学习课程
。例如,先修课程对
表示:想要学习课程
,你需要先完成课程
。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
示例 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)
本题是一道经典的「拓扑排序」问题,给定一个包含 个节点的有向图
,我们给出它的节点编号的一种排列。
如果满足:对于图中的任意一条有向边
,
在排列中都出现在
的前面。那么称该排列是图
的「拓扑排序」。
根据上述的 定义,我们可以得出两个结论:
如果图
中存在环(即图
不是「有向无环图」),那么图
不存在拓扑排序。
这是因为假设图中存在环
,那么
在排列中必须出现在
的前面,但
同时也必须出现在
的前面,因此无法满足这个要求
如果图
是有向无环图,那么它的拓扑排序可能不止一种。
举一个最极端的例子,如果图
值包含
个节点却没有任何边,那么任意一种编号的排列都可以作为拓扑排序。
复杂度分析
时间复杂度:,遍历一个图需要访问所有节点和所有临边,
和
分别为节点数量和临边数量;
空间复杂度:,为建立邻接表所需额外空间,
长度为
,并存储
条临边的数据。
我的代码
class Solution {public int[] findOrder(int numCourses, int[][] prerequisites) {List<List<Integer>>edges=new ArrayList<>();int[]inedges=new int[numCourses];for (int i = 0; i < numCourses; i++) {edges.add(new ArrayList<>());}// 边和入度for (int i = 0; i < prerequisites.length; i++) {inedges[prerequisites[i][0]]++;edges.get(prerequisites[i][1]).add(prerequisites[i][0]);}LinkedList<Integer>queue=new LinkedList<>();// 入度为0的结点u入队for (int i = 0; i < numCourses; i++) {if(inedges[i]==0) queue.addLast(i);}ArrayList<Integer>res=new ArrayList<>();while(!queue.isEmpty()){int u=queue.pollFirst();res.add(u); // 先学习unumCourses--;for (int i = 0; i < edges.get(u).size(); i++) {int v=edges.get(u).get(i); // 得到结点vinedges[v]--; //入度减少if(inedges[v]==0)queue.addLast(v);}}if(numCourses!=0) res.clear(); // 有环需要返回空数组return res.stream().mapToInt(Integer::intValue).toArray();}}
复杂度分析
时间复杂度:,遍历一个图需要访问所有节点和所有临边,
和
分别为节点数量和临边数量;
空间复杂度:,为建立邻接表所需额外空间,
长度为
,并存储
条临边的数据。
我的代码
import java.util.ArrayList;import java.util.List;class Solution {boolean valid = true;int index;public int[] findOrder(int numCourses, int[][] prerequisites) {index = numCourses - 1;List<List<Integer>> edges = new ArrayList<List<Integer>>();int[] visited = new int[numCourses];int[] res = new int[numCourses];for (int i = 0; i < numCourses; ++i) {edges.add(new ArrayList<Integer>());}// 添加边的信息for (int[] info : prerequisites) {edges.get(info[1]).add(info[0]);}// 遍历每一个结点做起始结点开始dfsfor (int i = 0; i < numCourses && valid; ++i) {if (visited[i] == 0) {dfs(i, visited, edges, res);}}if (!valid) return new int[0];return res;}public void dfs(int u, int[] visited, List<List<Integer>> edges, int[] res) {// 正在访问visited[u] = 1;// 一条由点 u -> v 的边for (int v : edges.get(u)) {// 未访问if (visited[v] == 0) {dfs(v, visited, edges, res);if (!valid) {return;}} else if (visited[v] == 1) {// 遇到一条正在访问的边,说明有环,刚才遍历过它valid = false;return;}}// 完成访问visited[u] = 2;res[index--] = u; // 【重点】:递归最底层完成后添加进答案,倒序添加}}
