一、问题:
该题目有深度优先和广度优先(贪心策略)两种解决办法。因为博主自己写出来的深度优先解法会超时,对比学习了官方解法之后,在这里写一下博主使用深度优先解法时候的思考。
二、原因:
1、博主使用的辅助数据只标记了节点的两种状态:
(1)True-当前遍历路径上,该节点被访问;
(2)False-当前遍历路径上,该节点没有被访问。
判别:当前遍历路径上,被访问的节点再一次被访问,则有环。
2、官方题解的辅助数据则标记了节点的三种状态:
(1)visited[0]:没有被访问;
(2)visited[1]:被访问过,出现在其后的节点没有被遍历完;
(3)visited[2]:被访问过,并且遍历完了其后的节点。
判别1:标记为visited[1]的节点再一次被访问,则有环。
判别2:标记为visited[2]的节点无需再作为一条路径的头节点被访问。
三、解决
1、改进前
首先建立图,随后对图进行遍历,使用visited辅助数据用以判别图中是否有环。
class Solution {
//标记是否被访问过
boolean[] visited;
//标记是否有环
boolean hasCycle = false;
//建立图
List<List<Integer>> graph;
public boolean canFinish(int numCourses, int[][] prerequisites) {
graph = new ArrayList<List<Integer>>();
for (int i = 0; i < numCourses; i++){
graph.add(new ArrayList<Integer>());
}
for (int[] edge : prerequisites){
graph.get(edge[1]).add(edge[0]);
}
visited = new boolean[numCourses];
//将每一个节点都作为一个路径的头节点被访问一次,防止访问不到单独的节点
for (int i = 0; i < numCourses; i++){
//如果已经发现有环,直接返回
if (hasCycle){
return !hasCycle;
}
traverse(graph, i);
}
return !hasCycle;
}
public void traverse(List<List<Integer>> graph, int s){
//如果有环,直接返回
if (hasCycle){
return;
}
//访问了已经被访问的节点,有环,返回
if (visited[s]){
hasCycle = true;
return;
}
//访问节点,标记为true
visited[s] = true;
for (int i : graph.get(s)){
traverse(graph, i);
}
//回溯,标记为false
visited[s] = false;
}
}
2、改进后
为了方便发现两者的不同,只留有改进后部分的注释。
class Solution {
//boolean类型数组改为整形数组,为了标记更多状态
int[] visited;
boolean hasCycle = false;
List<List<Integer>> graph;
public boolean canFinish(int numCourses, int[][] prerequisites) {
graph = new ArrayList<List<Integer>>();
for (int i = 0; i < numCourses; i++){
graph.add(new ArrayList<Integer>());
}
for (int[] edge : prerequisites){
graph.get(edge[1]).add(edge[0]);
}
visited = new int[numCourses];
for (int i = 0; i < numCourses; i++){
if (hasCycle){
return !hasCycle;
}
traverse(graph, i);
}
return !hasCycle;
}
public void traverse(List<List<Integer>> graph, int s){
if (hasCycle){
return;
}
if (visited[s] == 1){
hasCycle = true;
return;
}
//标记为visited[2]的节点无需再作为一条路径的头节点被访问。
/*
*举个例子
*输入[[2,1],[3,2]]
*形成图:
*1->2
*2->3
*先将节点1作为一条路径的节点被访问,此时会形成路径1->2->3
*改进前会继续将节点2作为一条路径的节点被访问,此时会形成路径2->3
*改进后节点2会被标记为visited[2],则省略这次访问,减少时间消耗。
*/
if (visited[s] == 2){
return;
}
visited[s] = 1;
for (int i : graph.get(s)){
traverse(graph, i);
}
visited[s] = 2;
}
}
四、总结
改进前的方法其实可以访问到所有可能存在路径,类似于排列组合,所以耗时更多。
不过对于有前后依赖的情形,比如:DAG(有向无环图)、拓扑排序。改进后的代码更适用。