题目:LeetCode207. Course Schedule。在这里我提供三种解法:
解法一:
寻找从入度为0的点,删掉从它们出发的边。持续进行会构造新的入度为0的点。如果不能进行到底,则是带环的:ac代码。处理过程有点类似bfs
解法二:
回溯+记忆:ac代码如下。其中使用名为checked的数组,只是为了保存已确定没有连接到环的点,不然会TLE。
class Solution {
boolean[] visits;
boolean[] checked;
public boolean canFinish(int numCourses, int[][] prerequisites) {
if(numCourses<2)
return true;
//构造图的邻接表
List<Integer>[] graph = new List[numCourses];
for(int v=0;v<numCourses;++v) {
graph[v] = new LinkedList<Integer>();
}
for(int[] e : prerequisites) {
graph[e[0]].add(e[1]);//构造图的邻接表
}
visits = new boolean[numCourses];
checked = new boolean[numCourses];
for(int v=0;v<numCourses;++v) {
if(!dfs(graph,numCourses,v))
return false;
}
return true;
}
private boolean dfs(List<Integer>[] graph, int V, int v) {
if(checked[v])
return true;
if(visits[v])
return false;
visits[v] = true;
for(int to : graph[v]) {
if(!dfs(graph,V,to)) {
return false;
}
}
visits[v] = false; //当没用使用checked,这句是必需的。但当使用了checked,没有这句也能ac
checked[v] = true;
return true;
}
}
解法三:
学习了经典书籍《算法导论》中dfs时每个点的发现时间、完成时间的特点:回边的特点是,边连接的两点都还未标记完成时间,到达点已经标记了发现时间,且小于出发点的发现时间(这一句是一定的,不用判断)。ac代码如下。此外也如同解法二,使用名为checked的数组来保存已知结果。
class Solution {
boolean[] checked;
int seq = 0;
Integer[] discover;
Integer[] finish;
public boolean canFinish(int numCourses, int[][] prerequisites) {
if (numCourses < 2)
return true;
// 构造图的邻接表
List<Integer>[] graph = new List[numCourses];
for (int v = 0; v < numCourses; ++v) {
graph[v] = new LinkedList<Integer>();
}
for (int[] e : prerequisites) {
if (e[0] == e[1])
return false;
graph[e[0]].add(e[1]);// 构造图的邻接表
}
discover = new Integer[numCourses];
finish = new Integer[numCourses];
checked = new boolean[numCourses];
for (int v = 0; v < numCourses; ++v) {
if (!dfs(graph, numCourses, v))
return false;
}
return true;
}
private boolean dfs(List<Integer>[] graph, int V, int v) {
discover[v] = ++seq;
if(checked[v]) { //如果确定此点没有连接到环,则可把这一块当作一个点处理
finish[v] = ++seq;
return true;
}
for (int to : graph[v]) {
if (discover[to]!=null && finish[to]==null /*&& discover[v]>discover[to]*/ || !dfs(graph, V, to))
return false;
}
finish[v] = ++seq;
checked[v] = true;
return true;
}
}
补注:上面对解法二、三的解释有点不够直观。本质就是遍历时每个点的状态会在这三种状态中变化:未访问(未发现)、已发现(刚开始对其dfs时)、已结束(即将退出其dfs时)。有环等价于有回边(backEdge),而回边的特点是指向已发现而还未结束的点(也就是当前结点的祖先)。