LeetCode-207. 课程表-Java-medium

题目链接

法一(拓扑排序)
    /**
     * 全局变量
     */
    private int[] inDegree; // 记录每个顶点的入度
    private List<Integer>[] graph;  // 有向图

    /**
     * 根据边的关系构造有向图并初始化顶点入度表
     *
     * @param numCourses
     * @param prerequisites
     * @return
     */
    private void createGraphByAdjList(int numCourses, int[][] prerequisites) {
        graph = new LinkedList[numCourses];
        for (int i = 0; i < numCourses; i++) {
            graph[i] = new LinkedList<>();
        }
        inDegree = new int[numCourses]; // 初始时所有顶点入度为0
        for (int[] pre : prerequisites) {
            int from = pre[1], to = pre[0];
            graph[from].add(to);
            inDegree[to]++; // to顶点有前驱顶点from,故入度加1
        }
    }

    /**
     * 法一(拓扑排序)
     * (1)拓扑排序,是将有向无环图G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,
     * 若边(u,v)∈E(G),则u在线性序列中出现在v之前
     * (2)若拓扑排序失败,则说明不是有向无环图
     *
     * @param numCourses
     * @param prerequisites
     * @return
     */
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        createGraphByAdjList(numCourses, prerequisites); // 初始化有向图和顶点入度表
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) { // 将所有入度为0的顶点入队
            if (inDegree[i] == 0) {
                queue.offer(i);
            }
        }
        int num = 0; // 记录加入拓扑序列的顶点数
        while (!queue.isEmpty()) {
            int from = queue.poll();     // 取队首入度为0的顶点from
            for (int to : graph[from]) { // 遍历顶点from的所有后继顶点
                inDegree[to]--;          // 后继顶点to入度减1
                if (inDegree[to] == 0) { // 若后继顶点to入度减为0,则入队
                    queue.offer(to);
                }
            }
            num++; // 加入拓扑序列的顶点数加1
        }
        return num == numCourses; // 加入拓扑序列的顶点数为numCourses,说明拓扑排序成功
    }
法二(DFS)
    /**
     * 全局变量
     */
    private List<Integer>[] graph;  // 有向图
    private int[] visited; // 记录DFS找环过程中的访问状态, 0:未访问,1:访问中,2:完成访问
    private boolean hasCircle = false; // 记录有向图是否有环

    /**
     * 根据边的关系构造有向图,并且用逆邻接表表示
     *
     * @param numCourses
     * @param prerequisites
     * @return
     */
    private void createGraphByInverseAdjList(int numCourses, int[][] prerequisites) {
        graph = new LinkedList[numCourses];
        for (int i = 0; i < numCourses; i++) {
            graph[i] = new LinkedList<>();
        }
        for (int[] pre : prerequisites) {
            int from = pre[1], to = pre[0];
            graph[to].add(from); // 逆邻接表
        }
    }

    /**
     * DFS需要通过逆邻接表实现,当访问一个顶点的时候,应该递归访问它的前驱顶点,直至前驱顶点没有前驱顶点为止
     *
     * @param to
     * @return
     */
    private void findCircle(int to) {
        visited[to] = 1; // 正在访问顶点to
        for (int from : graph[to]) {  // 遍历顶点to的所有前驱顶点
            if (visited[from] == 0) { // 若顶点to的前驱顶点from还没有访问过,则访问顶点from
                findCircle(from);
            } else if (visited[from] == 1) { // 访问途中若顶点to的前驱顶点访问状态为1,则说明有环
                hasCircle = true;
                return;
            }
        }
        visited[to] = 2; // 顶点to的所有前驱顶点访问完回溯后才算完成顶点to的访问
    }

    /**
     * 法二(DFS),较快
     * 通过DFS检测有向图中是否有环,只要存在环,课程就不能完成
     *
     * @param numCourses
     * @param prerequisites
     * @return
     */
    public boolean canFinish_2(int numCourses, int[][] prerequisites) {
        createGraphByInverseAdjList(numCourses, prerequisites); // 初始化有向图
        visited = new int[numCourses]; // 初始时所有顶点的访问状态为0
        for (int i = 0; i < numCourses; i++) {
            if (visited[i] == 0) {  // 如果顶点i没有访问过,则访问顶点i
                findCircle(i);
            }
        }
        return hasCircle ? false : true; // 有环返回false,无环返回true
    }
本地测试
        /**
         * 207. 课程表
         */
        lay.showTitle(207);
        Solution207 sol207 = new Solution207();
        int numCourses207 = 5;
        int[][] prerequisites207 = new int[][]{{1, 4}, {2, 4}, {3, 1}, {3, 2}};
        arrayOpt.showIntTwoDimArray(prerequisites207, prerequisites207.length);
        System.out.println(sol207.canFinish(numCourses207, prerequisites207));
        System.out.println(sol207.canFinish_2(numCourses207, prerequisites207));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值