🌈🌈😄😄
欢迎来到茶色岛独家岛屿,本期将为大家揭晓LeetCode 207. 课程表,做好准备了么,那么开始吧。
🌲🌲🐴🐴
一、题目名称
LeetCode 207. 课程表
二、题目要求
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
三、相应举例
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
四、限制要求
- 1 <= numCourses <= 105
- 0 <= prerequisites.length <= 5000
- prerequisites[i].length == 2
- 0 <= ai, bi < numCourses
- prerequisites[i] 中的所有课程对 互不相同
五、解决办法
在整个深度优先搜索的过程结束后,如果我们没有找到图中的环,那么栈中存储这所有的 nn 个节点,从栈顶到栈底的顺序即为一种拓扑排序。
我们将当前搜索的节点 u 标记为「搜索中」,遍历该节点的每一个相邻节点 v:
- 如果 v 为「未搜索」,那么我们开始搜索 v,待搜索完成回溯到 u;
- 如果 v 为「搜索中」,那么我们就找到了图中的一个环,因此是不存在拓扑排序的;
- 如果 v 为「已完成」,那么说明 v 已经在栈中了,而 u 还不在栈中,因此 u 无论何时入栈都不会影响到 (u, v) 之前的拓扑关系,以及不用进行任何操作。
当 u 的所有相邻节点都为「已完成」时,我们将 u 放入栈中,并将其标记为「已完成」
六、代码实现
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[] info : prerequisites) {
edges.get(info[1]).add(info[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;
}
}
注意事项:
-
本题定义edges邻接表,通过节点的索引,能找到该节点的后续节点。visited是一维数组edges.get(info[1]).add(info[0]);表示课程info[1]的前置课程是info[0]
- 方法dfs( )用于遍历图的深度优先搜索(DFS)算法。它遍历从给定起点'u'开始的图中的所有结点。在遍历过程中,使用一个visited数组来记录每个结点是否已经被访问。如果结点'u'没有被访问,则将其标记为已访问,并对与其相邻的所有结点进行遍历。如果遍历过程中发现结点'v'已经被访问过了,则说明图中存在环,valid标记为false,终止遍历。
- for (int v: edges.get(u))中的int v是用来接收List中的每个元素的,因为这里的edges.get(u)返回的是List<Integer>,所以用int来接收。如果是其他类型的列表那么就要用相应类型来接收。这样写的好处是可以直接遍历List中的每个元素,不用再手动写迭代器或者下标来遍历,更简洁易读。
- visited[u] = 2是已完成,0是未搜索,1是搜索中,每当进行dfs搜索时,都会判断该节点所处状态,若已完成,则回溯到上一节点,继续寻找是否有对应其他字节点,若无,则继续向上回溯,直到所有节点均已完成,返回true。若寻找过程中发现存在闭环现象,则直接返回false。
复杂度分析
时间复杂度: O(n+m),其中 n 为课程数,m 为先修课程的要求数。这其实就是对图进行深度优先搜索的时间复杂度。
空间复杂度:O(n+m)。题目中是以列表形式给出的先修课程关系,为了对图进行深度优先搜索,我们需要存储成邻接表的形式,空间复杂度为 O(n+m)。在深度优先搜索的过程中,我们需要最多 O(n) 的栈空间(递归)进行深度优先搜索,因此总空间复杂度为 O(n+m)。