LeetCode 热题 100_课程表(53_207_中等_C++)(图,拓扑排序)

ops/2025/1/17 17:00:30/

LeetCode 热题 100_课程表(53_207)

    • 题目描述:
    • 输入输出样例:
    • 题解:
      • 解题思路:
        • 思路一(广度优先搜索+拓扑排序):
      • 代码实现
        • 代码实现(思路一(拓扑排序)):
        • 以思路一为例进行调试

题目描述:

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

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

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

输入输出样例:

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

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

提示:
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对 互不相同

题解:

解题思路:

思路一(广度优先搜索+拓扑排序):

1、本题的要求是在选修某些课程之前需要一些先修课程,且两个课程不能互为先修课程。例子(false):学课程 ai 之前必学 bi,且学课程 bi 之前必学 ai,是不能进行下去的。通过问题的分析,我们发现此问题就是判断当前课程的学习是否构成一个环(有向图),判断环的存在,我们可以使用拓扑排序。在进行拓扑排序后,如果还存在入度为0结点则返回false,否则返回true。

2、具体思路如下:
① 在进行拓扑排序的时候我们需要从入度为0的结点开始,这样我们就要事先统计入度为0的结点,将这些结点加入队列中。
② 将队首结点出队,将此节点后继结点的入度减去(为了方便找到此节点的后继结点我们可以存储此节点和其后继结点的对应关系:邻接表)。



③ 重复上述过程直至队空为止,若此时存在入度>0的结点则返回false,否则返回true(我们也可以统计入度为0结点的数量,最后与总的结点数进行比较,相同则返回true,否则返回false)。

3、复杂度分析:
① 时间复杂度:O(n+m),其中 n 为课程数,m 为先修课程的要求数。首先遍历一遍所有的先修课程,统计每个课程的入度并统计每个课程的后继课程O(m)。将入度为0的课程压入栈中O(n),拓扑排序遍历课程结点O(n)。

② 空间复杂度:O(n+m),其中 n 为课程数,m 为先修课程的要求数。存储每个课程的入度O(n)和存储每个课程的后继课程(邻接表)O(n+m)(m可以理解为图中的边数)。

代码实现

代码实现(思路一(拓扑排序)):
class Solution {
private://in_degree存储每个结点的入度(下标代表当前结点)vector<int> in_degree;//存储每个结点对应的后继结点:邻接表(下标代表当前结点,与之对应的vector<int>为对应的出边)vector<vector<int>> thisNode_successors;public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {//Q队列用于存储入度为0的结点queue<int> Q;int n=0;//重置数组的大小,并将入度初始化为0thisNode_successors.resize(numCourses);in_degree.resize(numCourses,0);//统计每个课程的入度并统计每个课程的后继课程for (const auto & prerequisite : prerequisites){//统计每个课程的入度in_degree[prerequisite[0]]++; //统计每个课程的后继课程thisNode_successors[prerequisite[1]].push_back(prerequisite[0]);}//将一开始入度为0的结点入队,并统计入度为0的结点数for (int i = 0; i < numCourses; i++){if (in_degree[i]==0) Q.push(i);++n;}//将队首结点出队,将此节点后继结点的入度减去,将剩余入度为0的结点入队,并统计入度为0的结点数while (!Q.empty()){//将队首结点出队int currentNode= Q.front();Q.pop();//将此节点后继结点的入度减去,将剩余入度为0的结点入队,并统计入度为0的结点数for (auto &successor : thisNode_successors[currentNode]){in_degree[successor]--;if (in_degree[successor]==0){Q.push(successor);++n;} } }//将入度为0的结点与总的结点数进行比较,相同则返回true,否则返回falseif(n==numCourses) return true;return false;}
};
以思路一为例进行调试
#include<iostream>
#include <vector>
#include <queue>
using namespace std;class Solution {
private://in_degree存储每个结点的入度(下标代表当前结点)vector<int> in_degree;//存储每个结点对应的后继结点:邻接表(下标代表当前结点,与之对应的vector<int>为对应的出边)vector<vector<int>> thisNode_successors;public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {//Q队列用于存储入度为0的结点queue<int> Q;int n=0;//重置数组的大小,并将入度初始化为0thisNode_successors.resize(numCourses);in_degree.resize(numCourses,0);//统计每个课程的入度并统计每个课程的后继课程for (const auto & prerequisite : prerequisites){//统计每个课程的入度in_degree[prerequisite[0]]++; //统计每个课程的后继课程thisNode_successors[prerequisite[1]].push_back(prerequisite[0]);}//将一开始入度为0的结点入队,并统计入度为0的结点数for (int i = 0; i < numCourses; i++){if (in_degree[i]==0) Q.push(i);++n;}//将队首结点出队,将此节点后继结点的入度减去,将剩余入度为0的结点入队,并统计入度为0的结点数while (!Q.empty()){//将队首结点出队int currentNode= Q.front();Q.pop();//将此节点后继结点的入度减去,将剩余入度为0的结点入队,并统计入度为0的结点数for (auto &successor : thisNode_successors[currentNode]){in_degree[successor]--;if (in_degree[successor]==0){Q.push(successor);++n;} } }//将入度为0的结点与总的结点数进行比较,相同则返回true,否则返回falseif(n==numCourses) return true;return false;}
};int main(int argc, char const *argv[])
{//总共有 2 门课程int numCourses=2;//两门课程的对应关系vector<vector<int>> prerequisites={{1,0},{0,1}};Solution s;if(s.canFinish(numCourses,prerequisites)){cout<<"true";}else{cout<<"false";}return 0;
}

LeetCode 热题 100_课程表(53_207)原题链接
欢迎大家和我沟通交流(✿◠‿◠)


http://www.ppmy.cn/ops/150866.html

相关文章

【Redis】Redis事务和Lua脚本的区别

Redis事务 概念 事务&#xff1a;Redis事务是一组命令的集合&#xff0c;这些命令会被序列化地执行&#xff0c;中间不会被其他命令插入。 MULTI/EXEC&#xff1a;Redis事务通过MULTI命令开始&#xff0c;通过EXEC命令执行所有已入队的命令。 特点 原子性&#xff1a; 事务…

Python编程中的两种主要的编程模式

在Python编程中&#xff0c;有两种主要的编程模式被广泛使用&#xff1a;面向过程编程&#xff08;Procedural Programming&#xff09; 和 面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;。这两种模式各有优缺点&#xff0c;适用于不同的场景。 1. 面…

【软件工程】知识点总结(上)

重点章节&#xff1a;软件开发模型、敏捷开发方法、结构化开发方法、面向对象开发方法 目录 重点章节&#xff1a;软件开发模型、敏捷开发方法、结构化开发方法、面向对象开发方法 第一章&#xff1a;软件工程概述 1.1 内容简介 1.2 软件 1、软件的定义 2、软件的发展 3、软件的…

Android SystemUI——使用Dagger2加载组件(四)

SystemUI 是 Android 系统中的一个重要模块,负责绘制系统栏(如状态栏、导航栏)、锁屏、快捷设置等用户界面元素。由于其复杂性,良好的架构设计和依赖管理对于保持代码的可维护性和扩展性至关重要。这就是 Dagger2 在此发挥重要作用的地方。 一、Dagger2介绍 Dagger2 是一个…

基于微信小程序的游泳馆管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

多个页面一张SQL表,前端放入type类型

前端有三个页面需要修改 1.List data () {return {// 类型queryParam: {type: "1",},type: 1,}method:{handleAdd () {this.$refs.modalForm.add(this.type)this.$refs.modalForm.title 新增this.$refs.modalForm.disableSubmit false},handleEdit (record) {thi…

go语言 goc覆盖率统计

前言 有些代码需要统计整体代码的自动化测试覆盖率&#xff0c;下面说一下这个覆盖率应该如何统计 实现过程 安装goc # Mac/AMD64 curl -s -L "https://github.com/qiniu/goc/releases/latest" | sed -nE s!.*"([^"]*-darwin-amd64.tar.gz)".*!ht…

Flutter插件制作、本地/远程依赖及缓存机制深入剖析(原创-附源码)

Flutter插件在开发Flutter项目的过程中扮演着重要的角色&#xff0c;我们从 ​​​​​​https://pub.dev 上下载添加到项目中的第三方库都是以包或者插件的形式引入到代码中的&#xff0c;这些第三方工具极大的提高了开发效率。 深入的了解插件的制作、发布、工作原理和缓存机…