你这个学期必须选修 numCourse
门课程,记为 0
到 numCourse-1
。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
示例 1:
1 2 3
| 输入: 2, [[1,0]] 输出: true 解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
|
示例 2:
1 2 3
| 输入: 2, [[1,0],[0,1]] 输出: false 解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
|
提示:
- 输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。详情请参见图的表示法。
- 你可以假定输入的先决条件中没有重复的边。
1 <= numCourses <= 10^5
此题可以看作判断一个有向图是否存在环,如果图中存在环,则不存在拓扑排序。如果图中不存在环,则可能存在不止一种拓扑排序。
不存在拓扑排序:

存在拓扑排序:

上图中存在拓扑排序中的一种为:[0, 2, 1, 3, 5, 6, 4]。
求拓扑序列方法:不断取下「入度」为零的结点直到图中没有结点或没有入度为零的结点,按取下结点的顺序就得到了拓扑排序。
边缘列表就是存储图的每一条边, [[1,0]]
代表存在一条结点 0 到结点 1 的边。
方法一:BFS
我们首先统计每个结点的出边信息和入度信息,首先将入度为零的结点加入队列,取下一个结点,并将该结点所指向的结点入度减一,同时将入度减为零的结点加入队列,不断重复这个步骤直到队列为空,取下结点的顺序就是拓扑排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| class Solution { List<List<Integer>> edges; int[] indeg;
public boolean canFinish(int numCourses, int[][] prerequisites) { edges = new ArrayList<List<Integer>>(); for(int i = 0; i < numCourses; i++){ edges.add(new ArrayList<Integer>()); }
indeg = new int[numCourses]; for(int[] edge : prerequisites){ edges.get(edge[1]).add(edge[0]); ++indeg[edge[0]]; }
Queue<Integer> queue = new LinkedList<>(); for(int i = 0; i < numCourses; i++){ if(indeg[i] == 0){ queue.offer(i); } } int visited = 0; while(!queue.isEmpty()){ ++visited; int u = queue.poll(); for(int v : edges.get(u)){ --indeg[v]; if(indeg[v] == 0){ queue.offer(v); } } } return visited == numCourses; } }
|
时间复杂度 O(n+m),其中 n 为课程数,m 为先修课程的要求数。空间复杂度 O(n+m)。
方法二:DFS
我们也可以使用深度优先搜索来找到拓扑排序,我们使用一个栈来存储拓扑排序。对于任意一个结点,我们首先访问该结点,继续访问该结点指向的结点,一直递归地进行这个操作,直到当前访问的结点出度为 0 ,就开始递归返回,在返回的过程中将结点加入栈中。对所有未搜索过的结点进行深度优先搜索,最后得到的栈中,从栈顶到栈底就是拓扑排序。
对于图中的任意结点有三个状态:
- 未搜索,还没有搜索到这个结点;
- 搜索中,已经搜索过这个结点,还未回溯到该结点,此时这个结点还没有入栈;
- 已完成,已经搜索过这个结点,已经将该结点入栈。
每一轮深度优先搜索开始时,我们选择一个未搜索过的结点。
算法流程:
由于我们不需要拓扑序列,只用判断它是否存在,我们只用一个布尔值来代替栈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class Solution { List<List<Integer>> edges; int[] visited; boolean valid = true;
public boolean canFinish(int numCourses, int[][] prerequisites) { edges = new ArrayList<List<Integer>>(); for(int i = 0; i < numCourses; i++){ edges.add(new ArrayList<Integer>()); } visited = new int[numCourses]; for(int[] edge : prerequisites){ edges.get(edge[1]).add(edge[0]); } for(int i = 0; i < numCourses && valid; i++){ if(visited[i] == 0){ dfs(i); } } return valid; }
public void dfs(int u){ visited[u] = 1; for(int v : edges.get(u)){ if(visited[v] == 0){ dfs(v); if(!valid){ return; } }else if(visited[v] == 1){ valid = false; return; } } visited[u] = 2; } }
|
时间复杂度 O(n+m),其中 n 为课程数,m 为先修课程的要求数。空间复杂度 O(n+m)。