力扣207. 课程表
力扣刷题 8

力扣207.课程表

原题,题目描述:
b64c1354-c33b-4eb4-84b3-ae52b5680d42.png
题目给出一个整型变量表示要修的课程数量,给出一个二维数组,第二维固定长度只有2,第一维表示后修课程,第二维表示先修课程,必须要先修完先修课程再修后修课程。二维数组的第一维长度不固定。整个二维数组给出了一组课程先后修关系,任务是根据这个二维数组和课程数量判断能不能修完课程。
这应该是一个比较典型的图论的应用,看完题目后的第一想法其实就是感觉就是根据二维数组构建一个图,然后判断这个图有没有环,有环则不行,没环就可以。
我自己思考了一下想出一种深度优先搜索的思路,根据二维数组建一张图,然后从每个节点出发深度优先搜索遍历,判断会不会回到当前节点。根据这个想法我写出了以下代码:

class Solution {
    public static class CanFinishNode {
        private ArrayList<CanFinishNode> next;
        private int val;

        public CanFinishNode(int val) {
            next = new ArrayList<>();
            this.val = val;
        }

        public void add(CanFinishNode node) {
            next.add(node);
        }

    }

    boolean flag = true;

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        HashMap<Integer, CanFinishNode> map = new HashMap<>();
        for (int[] prerequisite : prerequisites) {
            CanFinishNode node1;
            boolean flag1 = map.containsKey(prerequisite[0]);
            if (flag1) {
                node1 = map.get(prerequisite[0]);
            } else {
                node1 = new CanFinishNode(prerequisite[0]);
                map.put(prerequisite[0], node1);
            }
            CanFinishNode node2;
            boolean flag2 = map.containsKey(prerequisite[1]);
            if (flag2) {
                node2 = map.get(prerequisite[1]);
            } else {
                node2 = new CanFinishNode(prerequisite[1]);
                map.put(prerequisite[1], node2);
            }
            node2.add(node1);
        }

        map.forEach((integer, node) -> {
            canFinishset.add(node);
            flag = flag && canFinishDFS(node.next);
            canFinishset.remove(node);
        });

        return flag;
    }

    Set<CanFinishNode> canFinishset=new HashSet<>();

    public boolean canFinishDFS(ArrayList<CanFinishNode> node) {
        if (node == null) {
            return true;
        }

        boolean flag = true;
        for (CanFinishNode canFinishNode : node) {
            if (canFinishset.contains(canFinishNode)) {
                return false;
            }
            canFinishset.add(canFinishNode);
            flag = canFinishDFS(canFinishNode.next);
            if (!flag) {
                return false;
            }
            canFinishset.remove(canFinishNode);
        }
        return flag;
    }

}

但是这个解法非常耗时,所以没有通过结题,在48/53个用例超时了。仔细分析一下,遍历建图的时候需要耗时,又需要进入递归和哈希运算(包括散列表和set集合),虽然整体分析时间复杂度不算特别高,但是操作数非常多。
我实在是想不出来解法了,于是我看了一样官方解答,官方给出了两种解法,一种基于深度优先,一种基于广度优先。分别从出度和入度出发来做判断,我将两种算法看懂后写出了如下代码:

class Solution {
    public static class CanFinishNode {
        private ArrayList<Integer> next;
        private int val;

        public CanFinishNode(int val) {
            next = new ArrayList<>();
            this.val = val;
        }

        public void add(int node) {
            next.add(node);
        }
    }


    HashMap<Integer, CanFinishNode> map = new HashMap<>();

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] n = new int[numCourses];

        for (int[] i : prerequisites) {
            n[i[0]]++;
            if (!map.containsKey(i[0])) {
                CanFinishNode node = new CanFinishNode(i[0]);
                map.put(i[0], node);

            }

            if (!map.containsKey(i[1])) {
                CanFinishNode node = new CanFinishNode(i[1]);
                map.put(i[1], node);
                node.add(i[0]);
            }else {
                CanFinishNode node = map.get(i[1]);
                node.add(i[0]);
            }

        }

        ArrayDeque<Integer> queue = new ArrayDeque<>();
        ArrayList<Integer> visited = new ArrayList<>();
        for (int i = 0; i < n.length; i++) {
            if (n[i] == 0) {
                queue.addLast(i);
            }
        }
        while (!queue.isEmpty()) {
            Integer i = queue.removeLast();
            visited.add(i);
            CanFinishNode node = map.get(i);
            if (node!=null){
                ArrayList<Integer> next = node.next;
                for (Integer integer : next) {
                    n[integer]--;
                    if (n[integer] == 0) {
                        queue.addLast(integer);
                    }
                }
            }
        }
        return visited.size() == numCourses;
    }

}

提交后通过,但是打败了百分之16,并不是一个令人满意的结果。仔细看其中有可以优化的地方,首先就是这个类可以去掉,这里类的作用的存储值和next,我们可以直接使用一个二维数组替代掉。同时这里的visited可以直接退化成一个变量。优化后代码如下:

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] n = new int[numCourses];
        ArrayList<Integer>[] res=new ArrayList[numCourses];

        for (int i = 0; i < numCourses; i++) {
            res[i] = new ArrayList<>();
        }

        for (int[] i : prerequisites) {
            n[i[0]]++;
            res[i[1]].add(i[0]);
        }

        ArrayDeque<Integer> queue = new ArrayDeque<>();

        for (int i = 0; i < n.length; i++) {
            if (n[i] == 0) {
                queue.addLast(i);
            }
        }
        int visited=0;
        while (!queue.isEmpty()) {
            Integer i = queue.removeLast();
            visited++;
            ArrayList<Integer> next = res[i];
            for (Integer integer : next) {
                n[integer]--;
                if (n[integer] == 0) {
                    queue.addLast(integer);
                }
            }
        }
        return visited == numCourses;
    }
}

最后打败百分之80,算得上比较优秀的解法。

力扣207. 课程表
https://www.than.pw/archives/%E5%8A%9B%E6%89%A3207.-%E8%AF%BE%E7%A8%8B%E8%A1%A8
作者
than
发布于
更新于
许可