LeetCode(207 & 210):课程表 I II Course Schedule I II(Java)

234 篇文章 1 订阅
177 篇文章 0 订阅

2019.9.17 #程序员笔试必备# LeetCode 从零单刷个人笔记整理(持续更新)

课程表问题是一道经典的拓扑排序题,第一题要求判断能否完成课程(实际上是判断有向图中是否存在环),第二题在此基础上要求给出全图的拓扑排序。

拓扑排序可以用BFS的思路也可以用DFS的思路。

DFS拓扑排序

需要借助辅助栈存储遍历到的元素,在拓扑的最深处反向入栈。详细的算法说明可以看DFS拓扑排序算法说明

BFS拓扑排序

1.计算所有结点的入度,在优先队列中加入入度为0的结点

2.每次弹出队列结点并删去与结点相邻的下一边(相邻结点入度-1),并将入度为0的结点(当前起始结点)放入队列

3.元素出队的过程即为拓扑排序序列


传送门:课程表

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:
输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

说明:
输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。

提示:
这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。

传送门:课程表 II

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, return the ordering of courses you should take to finish all courses.

There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array.

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

示例 1:
输入: 2, [[1,0]] 
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:
输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
     因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

说明:
输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。

提示:
这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。



import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Stack;

/**
 *
 * There are a total of n courses you have to take, labeled from 0 to n-1.
 * Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]
 * Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?
 * 现在你总共有 n 门课需要选,记为 0 到 n-1。
 * 在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
 * 给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?
 *
 */

public class CourseSchedule {
    //拓扑排序
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        if(prerequisites.length == 0){
            return true;
        }
        //遍历每一条边,计算所有结点的入度,将边保存在map中
        HashMap<Integer, ArrayList<Integer>> map = new HashMap<>();
        int[] inDegree = new int[numCourses];
        for(int[] p : prerequisites){
            inDegree[p[0]]++;
            map.putIfAbsent(p[1], new ArrayList<>());
            map.get(p[1]).add(p[0]);
        }
        //在优先队列中加入入度为0的结点
        LinkedList<Integer> queue = new LinkedList<>();
        for(int i = 0; i < numCourses; i++){
            if(inDegree[i] == 0){
                queue.add(i);
            }
        }
        //result为拓扑排序结果,每次将入度为0的结点(当前起始结点)放入队列,每次弹出结点并删去与结点相邻的下一边(相邻结点入度-1)
        ArrayList<Integer> result = new ArrayList<>();
        while(!queue.isEmpty()){
            int curNum = queue.pollFirst();
            result.add(curNum);
            //提前退出
            if(result.size() > numCourses){
                return false;
            }
            //每次将curNum结点的下一结点的出度-1,相当于删去拓扑排序的下一条边,当出现入度为0的结点时,加入队列。
            ArrayList<Integer> nextNums = map.get(curNum);
            if(nextNums != null){
                for(int nextNum : nextNums){
                    if(--inDegree[nextNum] == 0){
                        queue.add(nextNum);
                    }
                }
            }
        }
        //当且仅当拓扑排序结果的结点数与原数目相符时可以通过
        return result.size() == numCourses;
    }

    //DFS
    public boolean canFinish2(int numCourses, int[][] prerequisites) {
        if(prerequisites.length == 0){
            return true;
        }
        HashMap<Integer, ArrayList<Integer>> map = new HashMap<>();
        for(int[] p : prerequisites){
            map.putIfAbsent(p[1], new ArrayList<>());
            map.get(p[1]).add(p[0]);
        }
        //0:未访问,1:DFS正在访问,2:已访问
        int[] marked = new int[numCourses];

        for(int i = 0; i < numCourses; i++){
            if(hasCircle(i, map, marked)){
                return false;
            }
        }
        return true;
    }

    private boolean hasCircle(int curNum, HashMap<Integer, ArrayList<Integer>> map, int[] marked){
        if(marked[curNum] != 0){
           return marked[curNum] == 1 ? true : false;
        }
        marked[curNum] = 1;
        ArrayList<Integer> nextNums = map.get(curNum);
        if(nextNums != null){
            for(int nextNum : nextNums){
                if(hasCircle(nextNum, map, marked)){
                    return true;
                }
            }
        }
        marked[curNum] = 2;
        return false;
    }

    //课程表 II
    //返回你为了学完所有课程所安排的学习顺序。
    //可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
    public int[] findOrder2(int numCourses, int[][] prerequisites) {
        //遍历每一条边,计算所有结点的入度,将边保存在map中
        HashMap<Integer, ArrayList<Integer>> map = new HashMap<>();
        int[] inDegree = new int[numCourses];
        for(int[] p : prerequisites){
            inDegree[p[0]]++;
            map.putIfAbsent(p[1], new ArrayList<>());
            map.get(p[1]).add(p[0]);
        }
        //在优先队列中加入入度为0的结点
        LinkedList<Integer> queue = new LinkedList<>();
        for(int i = 0; i < numCourses; i++){
            if(inDegree[i] == 0){
                queue.add(i);
            }
        }
        //result为拓扑排序结果,每次将入度为0的结点(当前起始结点)放入队列,每次弹出结点并删去与结点相邻的下一边(相邻结点入度-1)
        ArrayList<Integer> result = new ArrayList<>();
        while(!queue.isEmpty()){
            int curNum = queue.pollFirst();
            result.add(curNum);
            //提前退出
            if(result.size() > numCourses){
                return new int[0];
            }
            //每次将curNum结点的下一结点的出度-1,相当于删去拓扑排序的下一条边,当出现入度为0的结点时,加入队列。
            ArrayList<Integer> nextNums = map.get(curNum);
            if(nextNums != null){
                for(int nextNum : nextNums){
                    if(--inDegree[nextNum] == 0){
                        queue.add(nextNum);
                    }
                }
            }
        }
        //当且仅当拓扑排序结果的结点数与原数目相符时可以通过
        if(result.size() == numCourses){
            int[] scheduel = new int[numCourses];
            for(int i = 0; i < numCourses; i++){
                scheduel[i] = result.get(i);
            }
            return scheduel;
        }else{
            return new int[0];
        }
    }

    //DFS + 辅助栈
    //算法说明:https://leetcode-cn.com/problems/course-schedule-ii/solution/ke-cheng-biao-ii-by-leetcode/
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] marked = new int[numCourses];
        HashMap<Integer, ArrayList<Integer>> map = new HashMap<>();
        for(int[] p : prerequisites){
            map.putIfAbsent(p[1], new ArrayList<>());
            map.get(p[1]).add(p[0]);
        }

        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < numCourses; i++) {
            if (dfs(i, map, marked, stack)) {
                return new int[0];
            }
        }

        //DFS通过则说明无环,且结点均在栈中
        int[] scheduel = new int[numCourses];
        for(int i = 0; i < numCourses; i++){
            scheduel[i] = stack.pop();
        }
        return scheduel;
    }

    private boolean dfs(int curNum, HashMap<Integer, ArrayList<Integer>> map, int[] marked, Stack<Integer> stack) {
        if(marked[curNum] != 0){
            return marked[curNum] == 1 ? true : false;
        }
        marked[curNum] = 1;
        ArrayList<Integer> nextNums = map.get(curNum);
        if(nextNums != null){
            for(int nextNum : nextNums){
                if(dfs(nextNum, map, marked, stack)){
                    return true;
                }
            }
        }
        marked[curNum] = 2;
        //在拓扑的最深处反向入栈
        stack.add(curNum);
        return false;
    }
}




#Coding一小时,Copying一秒钟。留个言点个赞呗,谢谢你#

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值