LeetCode 207. Course Schedule
2020-05-31 06:34:11 # leetcode # core problems

Problem

LeetCode 207. Course Schedule

1. 题目简述

一共有n门课程需要修,分别是从0到n-1。有些课程需要进行先修课程,我们以数组的形式给出,例如:[0, 1],就是说如果要修课,必须要上过1课。

现在给出numCourses门课程,和一个prerequisites数组,问是否能有一种修课方式,能够使得孙俪修完n门课程。

2. 算法思路

参考解法:花花酱LeetCode

也是一道经典老题目了,拓扑排序的经典问题,这里这道题我们使用BFS,下面这道类似的题目我们来使用DFS,体会一下差别。。LeetCode 210. Course Schedule II

BFS

其实所谓的prerequesites数组就是一个有向图,我们需要做的是在这个有向图中寻找是否存在环,如果存在环,则说明不可能存在一个拓扑排序;如果不存在环,则说明存在拓扑排序。

那么我们如何去查找环呢?两种做法,DFS和BFS,这里我们使用BFS吧。

BFS寻找拓扑排序的思路如下:

1. 统计整张图所有vertex的入度(indegree);
2. 找出入度为0的点,放入一个queue或者stack中,无所谓;
3. pop或者poll出一个节点作为当前节点,然后将该节点所指向的节点的入度更改,然后将新的入度为0的顶点添加到栈顶或者队列后;
4. 重复2和3的操作,直至stack或者queue为空,如果已经遍历完所有节点,那么我们pop的顺序就是一种拓扑序。

这里只是找是否存在,所以没必要存当前的拓扑序,代码如下:

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

class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
ArrayList<Integer>[] graph = new ArrayList[numCourses];
for (int i = 0; i < graph.length; i++) {
graph[i] = new ArrayList();
}
int[] indegree = new int[numCourses];
for (int[] prerequisite: prerequisites) {
indegree[prerequisite[0]]++;
graph[prerequisite[1]].add(prerequisite[0]);
}

Queue<Integer> zeroIndegrees = new LinkedList();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
zeroIndegrees.add(i);
}
}

List<Integer> topoSort = new ArrayList();
while (!zeroIndegrees.isEmpty()) {
int temp = zeroIndegrees.poll();
topoSort.add(temp);
// 减少所有它指向的节点的入度
for (int vertex : graph[temp]) {
indegree[vertex]--;
if (indegree[vertex] == 0) {
zeroIndegrees.add(vertex);
}
}
}

return (topoSort.size() == numCourses);
}
}

DFS

对于DFS而言,其实也有多种DFS的方式,例如 DFS + 回溯,我们可以每次从不同节点开始访问,每个节点有两种状态,访问和未访问。然后从0到n-1进行DFS遍历,每次退出的时候将节点重置为false。为什么要这样做呢?因为如果我们不将其重置为false,DFS到某节点若是已访问的状态,我们不知道它是在当前DFS的访问路径中还是说已经完全访问完毕的状态,这样做会导致大量的重复计算,如下实例,可以自行画图查看一下。

n = 4
prerequisites = [[1, 0], [1, 3], [2, 1]]

时间复杂度:O(E+ V^2);E是构建图的时间,V ^ 2是因为最坏情况下是一条线性的课程依赖关系,所以最坏是V ^ 2的复杂度。

空间复杂度:O(V+ E);构建图是O(V + E),isVisited变量是O(V),最坏情况下我们需要调用V层栈来储存我们递归函数,因此整体是O(3V + E),也就是O(v + E)。

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
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
ArrayList<Integer>[] graph = new ArrayList[numCourses];
boolean[] isVisited = new boolean[numCourses];
for (int i = 0; i < numCourses; i++) {
graph[i] = new ArrayList();
}

// build the whole graph
for (int[] prerequisite: prerequisites) {
graph[prerequisite[1]].add(prerequisite[0]);
}

//DFS确认是否有环
for (int i = 0; i < numCourses; i++) {
if (backtrack(graph, isVisited, i)) {
return false;
}
}

return true;
}

private boolean backtrack(ArrayList<Integer>[] graph, boolean[] isVisited, int vertex) {
if (isVisited[vertex]) {
return true;
} else {
isVisited[vertex] = true;
for (int next : graph[vertex]) {
if (backtrack(graph, isVisited, next)) {
return true;
}
}
isVisited[vertex] = false;
return false;
}
}
}

DFS优化

看到上面的解,我们发现会有很多重复的计算,我们真的需要通过回溯来重置状态么?其实并不是这样,只要我们加一个visiting的状态即可,和unvisited分开,如果发现访问的节点是visiting状态,则说明有环,退出;若是visited,说明完全没问题,当前节点记录后直接返回;unvisited的话就正常DFS,将节点设为visiting再继续。最终结束的时候将此节点设为visited。

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
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
ArrayList<Integer>[] graph = new ArrayList[numCourses];
int[] isVisited = new int[numCourses];
Arrays.fill(isVisited, -1);// -1 unvisited, 0 visiting, 1 visited
for (int i = 0; i < numCourses; i++) {
graph[i] = new ArrayList();
}

// build the whole graph
for (int[] prerequisite: prerequisites) {
graph[prerequisite[1]].add(prerequisite[0]);
}

//DFS确认是否有环
for (int i = 0; i < numCourses; i++) {
if (dfs(graph, isVisited, i)) {
return false;
}
}

return true;
}

// 有环return true,无环return false
private boolean dfs(ArrayList<Integer>[] graph, int[] isVisited, int vertex) {
if (isVisited[vertex] == 1) {
return false;
} else if (isVisited[vertex] == 0) {
return true;
} else {
isVisited[vertex] = 0;
for (int next : graph[vertex]) {
if (dfs(graph, isVisited, next)) {
return true;
}
}
isVisited[vertex] = 1;
return false;
}
}
}