LeetCode 207.课程表

129 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目:假如你是一个学生,给定你在这个学期所必修得num门课程,记为0num-1。选修课程必须按照规则来,即想学一门课程必须完成这个课程得先修课程。请判断是否可能完成所有课程的学习。

解题思路

先修课程的判断实际上是图的拓扑排序,也即判断一个有向无环图的拓扑排序是否存在。

拓扑排序的生成步骤为首先找到入度为0的节点,之后将这个节点的出度节点的入度更新,之后找到下一个入度为0的节点,不断重复直到没有入度为0的节点。

那么对于本题,我们可以首先记录每个课程的入度节点,这些入度节点的个数即为需要先修的课程数量。之后还需要将将本课程作为先修课程的课程给记录下来,此处使用的都是map。如果有入度为0的节点,也就是该课程不需要任何的先修课程,此时可以将本课程入队,之后根据队列是否为空循环出队,出队时更新以此课程作为先修课程的课程入度,如果以此课程为入度课程的课程的入度为0,则将此课程入队,最后看入度map中的课程是否存在入度不为0的课程即可,代码如下:

public boolean canFinish(int numCourses, int[][] prerequisites) {
        HashMap<Integer, Integer> inDgree = new HashMap<>();
        HashMap<Integer, List<Integer>> map = new HashMap<>();
        for(int i=0;i<numCourses;++i){
            inDgree.put(i, 0);
        }
        for(int[] pre:prerequisites){
            if(!map.containsKey(pre[1])){
                map.put(pre[1], new ArrayList<>());
            }
            map.get(pre[1]).add(pre[0]);
            inDgree.put(pre[0], inDgree.get(pre[0])+1);
        }
        Deque<Integer> deque = new LinkedList<>();
        for(int i=0;i<inDgree.size();i++){
            if(inDgree.get(i)==0){
                deque.addLast(i);
            }
        }
        while(!deque.isEmpty()){
            Integer pollFirst = deque.pollFirst();
            if(!map.containsKey(pollFirst)) continue;
            for(int value:map.get(pollFirst)){
                inDgree.put(value, inDgree.get(value) - 1);
                if(inDgree.get(value)==0){
                    deque.addLast(value);
                }
            }
        }
        for(Integer value:inDgree.keySet()){
            if(inDgree.get(value)!=0) return false;
        }
        return true;
    }

最终耗时16ms,实际上可以对上述代码进行优化,使用一个变量来记录当前处队的节点个数,最后判断当前出队节点个数是否等于所需要修的课程数即可。这样时间少了一个O(n)O(n)。如下出队之后的代码改为:

		int visited = 0;
        while(!deque.isEmpty()){
            visited ++ ;
            Integer pollFirst = deque.pollFirst();
            if(!map.containsKey(pollFirst)) continue;
            for(int value:map.get(pollFirst)){
                inDgree.put(value, inDgree.get(value) - 1);
                if(inDgree.get(value)==0){
                    deque.addLast(value);
                }
            }
        }
        return visited == numCourses;
	}

最终耗时10ms,上述代码还可以进一步优化,我们存储入度节点使用的是map,在代码执行过程中,我们需要对入度节点频繁的查询更新,因此入度节点的数据结构我们可以采用数组来实现,必然可以加速代码的执行速度,代码如下:

public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] inDgree = new int[numCourses];
        HashMap<Integer, List<Integer>> map = new HashMap<>();
        for(int[] pre:prerequisites){
            if(!map.containsKey(pre[1])){
                map.put(pre[1], new ArrayList<>());
            }
            map.get(pre[1]).add(pre[0]);
            inDgree[pre[0]]++;
        }
        Deque<Integer> deque = new LinkedList<>();
        for(int i=0;i<inDgree.length;i++){
            if(inDgree[i]==0){
                deque.addLast(i);
            }
        }
        int visited = 0;
        while(!deque.isEmpty()){
            visited ++ ;
            Integer pollFirst = deque.pollFirst();
            if(!map.containsKey(pollFirst)) continue;
            for(int value:map.get(pollFirst)){
                inDgree[value]--;
                if(inDgree[value]==0){
                    deque.addLast(value);
                }
            }
        }
        return visited == numCourses;
    }

这样代码最终耗时为6ms。