LeetCode HOT 100 —— 207 .课程表

题目

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。

例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

在这里插入图片描述

思路

拓扑排序:

用有向图表示依赖关系:

  • 示例:n = 6,先决条件表:[[3, 0], [3, 1], [4, 1], [4, 2], [5, 3], [5, 4]]
  • 0, 1, 2 没有先修课,可以直接选。其余的课,都有两门先修课
  • 用有向图来表示依赖关系:
    在这里插入图片描述
  • 上图是一个有向无环图,把一个 有向无环图 转成 线性的排序 就是 拓扑排序
  • 有向图中有 入度出度 两个概念,在本例中,顶点 0、1、2 的入度为 0,顶点 3、4、5 的入度为 2

选课顺序:

每次只能选入度为0的课,因为它不依赖别的课,如选了0,课3的入度由 2 变 1,接着选1,课 3 的入度变 0,课 4 的入度由 2 变 1,接着选 2,课 4 的入度变 0

然后现在,课 3 和课 4 的入度都为 0,继续选入度为 0 的课……直到选不到入度为 0 的课

类似BFS的思想:

  • 让入度为 0 的课入队列,它们是能直接选的课
  • 然后逐个出队列,出队列代表着该课被选,需要减小相关课(依赖出队列课的课)的入度
  • 如果相关课的入度新变为 0,安排它入队列、再出队列……直到没有入度为 0 的课可入队列

所需的数据结构:

  • 入度数组:课程号0到n-1作为索引,通过遍历先决条件表求出对应的初始入度
  • 邻接表:用哈希表记录依赖关系
    (1)key:课号
    (2)value: 依赖这门课的后续课(数组)

判断是否修完所有课程:

  1. BFS结束时,如果仍有课的入度不为0,无法被选,就无法完成所有课程
  2. 或者用一个变量count记录入队列的顶点个数,判断最后count是否等于总课程数

java代码如下:

class Solution{
	// 节点的入度: 使用数组保存每个节点的入度
	public boolean canFinish(int numCourses, int[][] prerequisites){
		// 1.课号和对应的入度
		Map<Integer,Integer> inDegree = new HashMap<>();
		// 将所有的课程先放入
		for(int i = 0; i < numCourses; i++){
			inDegree.put(i,0);
		}
		// 2.依赖关系, 依赖当前课程的后序课程
		Map<Integer,List<Integer>> adj = new HashMap<>();
		
		//初始化入度和依赖关系
		for(int[] relate : prerequisites){//prerequisites里面是一个数组列表,每个元素就是一个int[]数组
			// (3,0), 想学3号课程要先完成0号课程, 更新3号课程的入度和0号课程的依赖(邻接表),即next依赖于cur
			int cur = relate[1];//需要先学习的前驱课程,相当于(3,0)的0
			int next = relate[0];//完成前驱课程之后才能学习的课程,相当于(3,0)的3
			// 1.更新入度,因为next依赖于cur,所以增加入度
			inDegree.put(next,inDegree.get(next) + 1);
			// 2.当前节点的邻接表
			if(!adj.containsKey(cur)){//如果不包括前驱课程
				adj.put(cur, new ArrayList<>());//更新依赖图
			}
			adj.get(cur).add(next);//因为next依赖于cur,所以添加依赖关系
		}
		
		//3.BFS, 将入度为0的课程放入队列, 队列中的课程就是可以直接学的课程
		Queue<Integer> q = new LinkedList<>();
		for(int key : inDegree.keySet()){
			if(inDegree.get(key) == 0){
				q.offer(key);
			}
		}
		// 取出一个节点, 对应学习这门课程
		// 遍历当前邻接表, 更新其入度; 更新之后查看入度, 如果为0, 加入到队列
		while(!q.isEmpty()){
			int cur = q.poll();
			// 遍历当前课程的邻接表, 更新后继节点的入度
			if(!adj.containsKey(cur)){
				continue;
			}
			List<Integer> successList = adj.get(cur);//返回的是依赖于cur的数组列表
			
			for(int k : successList){//遍历这个列表,里面所有的入度减少一
				inDegree.put(k, inDegree.get(k) - 1);
				if(inDegree.get(k) == 0){//如果发现有入度为0的,则入队列
					q.offer(k);
				}
			}
		}
		
		// 4.遍历入队, 如果还有课程的入度不为0, 返回fasle
		for(int key : inDegree.keySet()){
			if(inDegree.get(key) != 0){
				return false;
			}
		}
		return true;
	}
}

总结:

  1. 根据依赖关系,建立邻接表、入度数组
  2. 选取入度为0的数据,根据邻接表,减小依赖它的数据的入度
  3. 找出入度为0的数据,重复第2步
  4. 直到所有数据的入度为0,得到排序,如果有数据入度不为0,说明图中存在环
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HDU-五七小卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值