当前位置: 首页 > article >正文

拓扑排序 —— 2. 力扣刷题207. 课程表

在这里插入图片描述

题目链接:https://leetcode.cn/problems/course-schedule/description/
题目难度:中等
相关标签:拓扑排序 / 广度优先搜搜 BFS / 深度优先搜索 DFS

2.1 问题与分析

2.1.1 原题截图

在这里插入图片描述

2.1.2 题目分析

首先,理解题目后必须马上意识到考察的是 图论类型中的有向图问题,接着考虑到有向图之间的关系,可以想到我们在本系列第一篇 博客 提到的 拓扑排序

接着,这里将题目进一步简化:

给你 numCourses 门课程,和若干课程依赖关系 prerequisites,问你能否顺利修完所有课程。

换句话说:

  • 每门课程是一个节点。

  • 依赖关系是一个有向边(比如 [1, 0] 表示:想学 1,必须先学 0)。

这就构成了一个有向图,问题变成了:

这个图有没有环?

  • 有环 = 某些课程互相依赖,永远无法完成。

  • 无环 = 可以通过拓扑排序找到学习顺序。

在本系列的第一篇博客已经提到过,拓扑排序可以用来解决 判断有向图中是否有环问题

最后已经大概怎么知道解决问题了,那么请回答这个问题 如何通过拓扑排序解决有向图中是否有环问题

2.2 解法 1 —— 基于广度优先搜索(BFS)的拓扑排序(Kahn 算法)

不急着写代码,我们先用文字的形式,描述清楚解题思路。

2.2.1 解题思路

解题思路

  1. 构建图结构,使用二维数组存储,记作 graph
  2. 统计每个结点的入度,使用一维数组存储,记作 inCounts
  3. 维护一个队列 Q,将 inCounts 中入度为 0 的元素入队;
  4. 基于队列进行操作,将访问的结点存储到 results 数组中。
    a. 对于队中每个元素 e,将它的相邻结点的入度 减 1
    b. 如果 减 1 后的结点入度为 0,加入队列 Q
    c. 将 e 写入 results 数组。
    d. e 出列,循环此项操作。
  5. 判定 results 数组长度是否与所有元素数目相等。相等表示可以正常访问所有结点,原拓扑 无环,返回 true,否则返回 false。

复杂度分析

  • 时间复杂度: O(n+m),其中 n 为课程数,m 为先修课程的要求数。
  • 空间复杂度: O(n+m)。

2.2.2 代码实现

前面已经将解题方法写明白了,写代码就很方便了,我们分别使用 C++ / python 与 java 三种语言实现。

基于 C++ 的代码实现

实现思路请参考前文,先理解思路再看代码。

class Solution {
public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {vector<int> inCount(numCourses);vector<vector<int>> graph(numCourses);// [0, 1] 表示,需要先学习课程 1,才能学习课程 0// Step 1: 构建图// Step 2: 统计入度for (const auto& par: prerequisites) {graph[par[1]].push_back(par[0]);inCount[par[0]]++;}// Step 3: 初始化队列queue<int> Q;for (int i=0; i<numCourses; i++) {if (inCount[i] == 0) {Q.push(i);}}// Step 4:vector<int> results;while (!Q.empty()) {auto front = Q.front();Q.pop();results.push_back(front);for (auto v: graph[front]) {inCount[v]--;if (inCount[v] == 0) {Q.push(v);}}}return results.size() == numCourses;}
};
基于 python 的代码实现

实现思路请参考前文,先理解思路再看代码。

class Solution:def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:inCount = [0] * numCoursesgraph = [[] for _ in range(numCourses)]# [0, 1] 表示,需要先学习课程 1,才能学习课程 0# Step 1: 构建图# Step 2: 统计入度for par in prerequisites:graph[par[1]].append(par[0])inCount[par[0]] += 1# Step 3: 初始化队列Q = deque()for i in range(numCourses):if inCount[i] == 0:Q.append(i)# Step 4:results = []while Q:front = Q.popleft()results.append(front)for v in graph[front]:inCount[v] -= 1if inCount[v] == 0:Q.append(v)return len(results) == numCourses
基于 java 的代码实现

实现思路请参考前文,先理解思路再看代码。

class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {int[] inCount = new int[numCourses];List<List<Integer>> graph = new ArrayList<>();for (int i = 0; i < numCourses; i++) {graph.add(new ArrayList<>());}// [0, 1] 表示,需要先学习课程 1,才能学习课程 0// Step 1: 构建图// Step 2: 统计入度for (int[] par : prerequisites) {graph.get(par[1]).add(par[0]);inCount[par[0]]++;}// Step 3: 初始化队列Queue<Integer> Q = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (inCount[i] == 0) {Q.offer(i);}}// Step 4:List<Integer> results = new ArrayList<>();while (!Q.isEmpty()) {int front = Q.poll();results.add(front);for (int v : graph.get(front)) {inCount[v]--;if (inCount[v] == 0) {Q.offer(v);}}}return results.size() == numCourses;}
}

2.3 解法 2 —— 基于深度优先搜索(DFS)的拓扑排序

前面已经将解题方法写明白了,写代码就很方便了,我们分别使用 C++ / python 与 java 三种语言实现。

2.3.1 解题思路

解题思路

  1. 构建图,使用二维数组 graph 保存图的结构;
  2. 初始化过程:
    a. 初始化一维数组 visited,它的每个元素含有三个状态 0 表示未访问过,1 表示已经访问过,2 表示已经记录到最终结果数组中。注意这个地方必须保证更新顺序,也就是 0 -> 1 -> 2。
    b. 初始化 valid 变量,表示DFS过程中是否遇到 ,比如遇到环了就结束。
    c. 初始化 results 数组,用于记录已经访问过的路径。
  3. DFS 过程,该过程对每一个未访问过的结点进行考察,对于结点 e,完成操作包括:
    a. 更新 visited 数组,标记 e 结点已经被访问过。
    b. DFS 访问与 e 结点连接的其他结点。
    c. 如果下一个结点 visited 状态为 0, 则继续DFS;如果状态为 2,则说明有环,更新 valid 结束 DFS。
    d. 访问 e 结点的所有相邻结点后,将 e 记录到 results 中。
    e. 访问 e 结点以后,更新 visited[e] 的状态为 2,表示已经记录到 results 数组中。
  4. 判定 results 数组长度是否与所有元素数目相等。相等表示可以正常访问所有结点,原拓扑 无环,返回 true,否则返回 false。

2.3.2 代码实现

基于 C++ 的代码实现

实现思路请参考前文,先理解思路再看代码。

class Solution {
private:bool valid = true;vector<int> visited;vector<int> results;vector<vector<int>> graph;void dfs(int i) {visited[i] = 1;for (auto v: graph[i]) {if (visited[v] == 0) {dfs(v);} else if (visited[v] == 1) {valid = false;return;}}results.push_back(i);visited[i] = 2;}
public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {graph = vector<vector<int>>(numCourses);for (const auto& par: prerequisites) {graph[par[1]].push_back(par[0]);}visited = vector<int>(numCourses, 0);for (int i=0; i<numCourses && valid; i++) {if (visited[i] == 0) {dfs(i);}}return results.size() == numCourses;}
};
基于 python 的代码实现

实现思路请参考前文,先理解思路再看代码。

class Solution:def __init__(self):self.valid = Trueself.visited = []self.results = []self.graph = []def dfs(self, i: int):self.visited[i] = 1for v in self.graph[i]:if self.visited[v] == 0:self.dfs(v)if not self.valid:returnelif self.visited[v] == 1:self.valid = Falsereturnself.results.append(i)self.visited[i] = 2def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:self.graph = [[] for _ in range(numCourses)]for par in prerequisites:self.graph[par[1]].append(par[0])self.visited = [0] * numCoursesself.results = []self.valid = Truefor i in range(numCourses):if self.valid and self.visited[i] == 0:self.dfs(i)return len(self.results) == numCourses
基于 java 的代码实现

实现思路请参考前文,先理解思路再看代码。

class Solution {private boolean valid = true;private int[] visited;private List<Integer> results;private List<List<Integer>> graph;private void dfs(int i) {visited[i] = 1;for (int v : graph.get(i)) {if (visited[v] == 0) {dfs(v);if (!valid) return;} else if (visited[v] == 1) {valid = false;return;}}results.add(i);visited[i] = 2;}public boolean canFinish(int numCourses, int[][] prerequisites) {graph = new ArrayList<>();for (int i = 0; i < numCourses; i++) {graph.add(new ArrayList<>());}for (int[] par : prerequisites) {graph.get(par[1]).add(par[0]);}visited = new int[numCourses];results = new ArrayList<>();valid = true;for (int i = 0; i < numCourses && valid; i++) {if (visited[i] == 0) {dfs(i);}}return results.size() == numCourses;}
}

2.4 总结

力扣的这道题可以作为 拓扑排序模板题,因为理解题目容易,必须建立在对 拓扑排序 的两种方法的了解的基础上,才能完成。中等难度,比较适合新手练习。

就题目而言,这道题本身就是判断是否有环的问题,通过拓扑排序实现而言。

继续强调一下,做题的目的是为了更加熟悉拓扑排序的算法思想,算法套路,不能停留在解决问题本身

感谢各位小伙伴们的 阅读点赞评论关注 ~ 希望本文能帮助到各位,共勉 ~

在这里插入图片描述

Smileyan
2025.04.12 19:04

相关文章:

拓扑排序 —— 2. 力扣刷题207. 课程表

题目链接&#xff1a;https://leetcode.cn/problems/course-schedule/description/ 题目难度&#xff1a;中等 相关标签&#xff1a;拓扑排序 / 广度优先搜搜 BFS / 深度优先搜索 DFS 2.1 问题与分析 2.1.1 原题截图 2.1.2 题目分析 首先&#xff0c;理解题目后必须马上意识到…...

从入门到进阶:React 图片轮播 Carousel 的奇妙世界!

全文目录&#xff1a; 开篇语&#x1f590; 前言✨ 目录&#x1f3af; 什么是图片轮播组件&#xff1f;&#x1f528; 初识 React 中的轮播实现示例代码分析 &#x1f4e6; 基于第三方库快速实现轮播示例&#xff1a;用 react-slick优势局限性 &#x1f6e0;️ 自己动手实现一个…...

【STM32】ST7789屏幕驱动

目录 CubeMX配置 配置SPI 开DMA 时钟树 堆栈大小 Keil工程配置 添加两个group 添加文件包含路径 驱动编写 写单字节函数 写字函数 写多字节函数 初始化函数 设置窗口函数 情况一&#xff1a;正常的0度旋转 情况二&#xff1a;顺时针90度旋转 情况三&#xff1…...

深入理解 PyTorch 的 nn.Embedding:词向量映射及变量 weight 的更新机制

文章目录 前言一、直接使用 nn.Embedding 获得变量1、典型场景2、示例代码&#xff1a;3、特点 二、使用 iou_token nn.Embedding(1, transformer_dim) 并访问 iou_token.weight1、典型场景2、示例代码&#xff1a;3、特点 三、第一种方法在模型更新中会更新其值吗&#xff1f…...

10min速通Linux文件传输

实验环境 在Linux中传输文件需要借助网络以及sshd&#xff0c;我们可通过systemctl status sshd来查看sshd状态 若服务未开启我们可通过systemctl enable --now sshd来开启sshd服务 将/etc/ssh/sshd_config中的PermitRootLogin 状态修改为yes 传输文件 scp scp &#xff08;Sec…...

dify windos,linux下载安装部署,提供百度云盘地址

dify1.0.1 windos安装包百度云盘地址 通过网盘分享的文件&#xff1a;dify-1.0.1.zip 链接: 百度网盘 请输入提取码 提取码: 1234 dify安装包 linux安装包百度云盘地址 通过网盘分享的文件&#xff1a;dify-1.0.1.tar.gz 链接: 百度网盘 请输入提取码 提取码: 1234 1.安装…...

使用 TFIDF+分类器 范式进行企业级文本分类(二)

1.开场白 上一期讲了 TF-IDF 的底层原理&#xff0c;简单讲了一下它可以将文本转为向量形式&#xff0c;并搭配相应分类器做文本分类&#xff0c;且即便如今的企业实践中也十分常见。详情请见我的上一篇文章 从One-Hot到TF-IDF&#xff08;点我跳转&#xff09; 光说不练假把…...

《车辆人机工程-汽车驾驶操纵实验》

汽车操纵装置有哪几种&#xff0c;各有什么特点 汽车操纵装置是驾驶员直接控制车辆行驶状态的关键部件&#xff0c;主要包括以下几种&#xff0c;其特点如下&#xff1a; 一、方向盘&#xff08;转向操纵装置&#xff09; 作用&#xff1a;控制车辆行驶方向&#xff0c;通过转…...

[ABC400F] Happy Birthday! 3 题解

考虑正难则反。问题转化为&#xff1a; 一个环上有 n n n 个物品&#xff0c;颜色分别为 c o l i col_i coli​&#xff0c;每次操作选择两个数 i , j i, j i,j 使得 ∀ k ∈ [ i , j ] , c o l k c o l i ∨ c o l k 0 \forall k \in [i, j], col_k col_i \lor col_k …...

python高级编程一(生成器与高级编程)

@TOC 生成器 生成器使用 通过列表⽣成式,我们可以直接创建⼀个列表。但是,受到内存限制,列表容量肯定是有限的。⽽且,创建⼀个包含100万个元素的列表,不仅占⽤很⼤的存储空间,如果我们仅仅需要访问前⾯⼏个元素,那后⾯绝⼤多数元素占 ⽤的空间都⽩⽩浪费了。所以,如果…...

Go 字符串四种拼接方式的性能对比

简介 使用完整的基准测试代码文件&#xff0c;可以直接运行来比较四种字符串拼接方法的性能。 for 索引 的方式 for range 的方式 strings.Join 的方式 strings.Builder 的方式 写一个基准测试文件 echo_bench_test.go package mainimport ("os""stri…...

windows安装fastbev环境时,安装mmdetection3d出现的问题总结

出现的问题如下&#xff1a; C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.3\include\crt/host_config.h(160): fatal error C1189: #error: -- unsupported Microsoft Visual Studio version! Only the versions between 2017 and 2019 (inclusive) are supporte…...

单片机Day05---动态数码管显示01234567

一、原理图 数组索引段码值二进制显示内容00x3f0011 1111010x060000 0110120x5b0101 1011230x4f0100 1111340x660110 0110450x6d0110 1101560x7d0111 1101670x070000 0111780x7f0111 1111890x6f0110 11119100x770111 0111A110x7c0111 1100B120x390011 1001C130x5e0101 1110D140…...

【Python3教程】Python3基础篇之数据结构

博主介绍:✌全网粉丝22W+,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物联网、机器学习等设计与开发。 感兴趣的可…...

muduo库源码分析: One Loop Per Thread

One Loop Per Thread的含义就是&#xff0c;一个EventLoop和一个线程唯一绑定&#xff0c;和这个EventLoop有关的&#xff0c;被这个EventLoop管辖的一切操作都必须在这个EventLoop绑定线程中执行 1.在MainEventLoop中&#xff0c;负责新连接建立的操作都要在MainEventLoop线程…...

使用Python解决Logistic方程

引言 在数学和计算机科学中,Logistic 方程是描述人口增长、传播过程等现象的一种常见模型。它通常用于表示一种有限资源下的增长过程,比如动物种群、疾病传播等。本文将带领大家通过 Python 实现 Logistic 方程的求解,帮助你更好地理解这一经典数学模型。 1.什么是 Logist…...

AI Agent工程师认证-学习笔记(3)——【多Agent】MetaGPT

学习链接:【多Agent】MetaGPT学习教程 源代码链接(觉得很好,star一下):GitHub - 基于MetaGPT的多智能体入门与开发教程 MetaGPT链接:GitHub - MetaGPT 前期准备 1、获取MetaGPT (1)使用pip获取MetaGPT pip install metagpt==0.6.6#或者在国内加速安装镜像 #pip in…...

MCP结合高德地图完成配置

文章目录 1.MCP到底是什么2.cursor配置2.1配置之后的效果2.2如何进行正确的配置2.3高德地图获取key2.4选择匹配的模型 1.MCP到底是什么 作为学生&#xff0c;我们应该如何认识MCP&#xff1f;最近看到了好多跟MCP相关的文章&#xff0c;我觉得我们不应该盲目的追求热点的技术&…...

重读《人件》Peopleware -(5)Ⅰ管理人力资源Ⅳ-质量—若时间允许

20世纪的心理学理论认为&#xff0c;人类的性格主要由少数几个基本本能所主导&#xff1a;生存、自尊、繁衍、领地等。这些本能直接嵌入大脑的“固件”中。我们可以在没有强烈情感的情况下理智地考虑这些本能&#xff08;就像你现在正在做的那样&#xff09;&#xff0c;但当我…...

文献总结:AAAI2025-UniV2X-End-to-end autonomous driving through V2X cooperation

UniV2X 一、文章基本信息二、文章背景三、UniV2X框架1. 车路协同自动驾驶问题定义2. 稀疏-密集混合形态数据3. 交叉视图数据融合&#xff08;智能体融合&#xff09;4. 交叉视图数据融合&#xff08;车道融合&#xff09;5. 交叉视图数据融合&#xff08;占用融合&#xff09;6…...

制造一只电子喵 (qwen2.5:0.5b 微调 LoRA 使用 llama-factory)

AI (神经网络模型) 可以认为是计算机的一种新的 “编程” 方式. 为了充分利用计算机, 只学习传统的编程 (编程语言/代码) 是不够的, 我们还要掌握 AI. 本文以 qwen2.5 和 llama-factory 举栗, 介绍语言模型 (LLM) 的微调 (LoRA SFT). 为了方便上手, 此处选择使用小模型 (qwen2…...

如何查询node inode上限是多少?

在 Linux 系统中&#xff0c;inode 上限由文件系统的类型和格式化时的参数决定。不同文件系统&#xff08;如 ext4、XFS&#xff09;有不同的查询方法。以下是详细操作步骤&#xff1a; 1. 确认文件系统类型 首先确定目标磁盘分区的文件系统类型&#xff08;如 ext4、XFS&…...

Redis核心功能实现

前言 学习是个输入的过程&#xff0c;在进行输入之后再进行一些输出&#xff0c;比如写写文章&#xff0c;笔记&#xff0c;或者做一些技术串讲&#xff0c;虽然需要花费不少时间&#xff0c;但是好处很多&#xff0c;首先是能通过输出给自己的输入带来一些动力&#xff0c;然…...

驱动学习专栏--字符设备驱动篇--1_chrdevbase

字符设备驱动简介 字符设备是 Linux 驱动中最基本的一类设备驱动&#xff0c;字符设备就是一个一个字节&#xff0c;按照字节 流进行读写操作的设备&#xff0c;读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC 、 SPI &#xff0c; LCD 等等都是字符设备&…...

Python及C++中的列表

一、Python中的列表&#xff08;List&#xff09; Python的列表是动态数组&#xff0c;内置于语言中&#xff0c;功能强大且易用&#xff0c;非常适合算法竞赛。 1. 基本概念 定义&#xff1a;列表是一个有序、可变的序列&#xff0c;可以存储任意类型的元素&#xff08;整数…...

Oracle DROP、TRUNCATE 和 DELETE 原理

在 Oracle 11g 中&#xff0c;DROP、TRUNCATE 和 DELETE 是三种不同的数据清理操作&#xff0c;它们的底层原理和适用场景有显著差异 1. DELETE 的原理 类型&#xff1a;DML&#xff08;数据操作语言&#xff09; 功能&#xff1a;逐行删除表中符合条件的数据&#xff0c;保留…...

ida 使用记录

文章目录 伪代码-汇编hexstring快捷键 伪代码-汇编 流程图界面——F5——伪代码界面——再点Tab——流程图界面——再按空格——汇编界面流程图界面——空格——汇编界面 hex view - open subviews - hex dump string view - open subviews - string快捷键&#xff1a; sh…...

【连载3】基础智能体的进展与挑战综述

基础智能体的进展与挑战综述 从类脑智能到具备可进化性、协作性和安全性的系统 【翻译团队】刘军(liujunbupt.edu.cn) 钱雨欣玥 冯梓哲 李正博 李冠谕 朱宇晗 张霄天 孙大壮 黄若溪 2. 认知 人类认知是一种复杂的信息处理系统&#xff0c;它通过多个专门的神经回路协调运行…...

MacOs java环境配置+maven环境配置踩坑实录

oracl官网下载jdk 1.8的安装包 注意可能需要注册&#xff01;&#xff01;&#xff01; 下载链接&#xff1a;下载地址点击 注意晚上就不要下载了 报错400 &#xff01;&#xff01;&#xff01; 1.点击安装嘛 2.配置环境变量 export JAVA_HOME/Library/Java/Java…...

【Git】--- 企业级开发流程

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; Git 本篇博客我们讲解Git在企业开发中的整体流程&#xff0c;理解Git在实际企业开发中的高效设计。 &#x1f3e0; 企业级开发流程 一个软件从零开始到最…...