Leetcode题目—— 207.课程表

一、问题:

该题目有深度优先广度优先(贪心策略)两种解决办法。因为博主自己写出来的深度优先解法会超时,对比学习了官方解法之后,在这里写一下博主使用深度优先解法时候的思考。

二、原因:

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(有向无环图)、拓扑排序。改进后的代码更适用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值