Leetcode 每日一题:Course Schedule II

news2024/11/23 20:48:51

写在前面:

今天我们继续来看一道经典的图论问题,而这个问题可以说是跟我们一众学生的生活息息相关啊!我们每年都有很多需要完成的必修指标,每一个必修指标可能会有一个或多个先修要求,而我们需要决定是否能将这些课全都上一遍,这不就是咱们苦逼大学生每学期选课前的日常嘛!那既然如此,我们就来看看这道与我们生活息息相关的这道算法题吧~~

题目介绍:

题目信息:

  • 题目链接:https://leetcode.com/problems/course-schedule-ii/description/
  • 题目类型:DFS,Graph,Adjacent List,Topology
  • 题目难度:Medium,但其实我觉得作为一道 hard 也不是不可以
  • 题目来源:Google 高频面试真题

题目介绍:

  • 给定一个整数,代表所有需要修的课的总数,所有需要修的课为 0, ...., numCourses - 1
  • 给定一个数组,每一个元素是一个长度为 2 的小数组,每一个小数组的第一个为目标课程,第二个为这个目标课程所需要的先修 (prerequisites)
  • 找出一个可以把全部课上完的组合,返回这个组合
  • 如果不可能都上完,则返回一个空数组

题目想法:

图论转化:

这道题目的关键是在于找到对应关系,而这个对应关系就来源于 prerequisite,也就是先修, 即:a ---> b 一定得先上过 a 才能上 b

这样的话,这道题其实就是一个巨大的单向图的问题,每一个课都是一个节点,而我们可以从任何一个节点开始,只需要找到一个可以不重复的访问所有节点的策略就可以了。同时,这道题目可以允许多个起点,因为对于一些没有任何 prerequisite 的课,在图中表示为游离点,我们也是可以直接上的,所以我们只需要找有连接的点中,有没有内置的循环即可。当且仅当在图中的一个部分存在循环的时候,我们才无法上完所有的课

ADJ List

这道题的 adjacent List 也相对比较好想直接,我们只需要遍历所有的 prerequiste,将每一个prerequisite[0] 作为 dest,prerequisite[1] 作为 src 就可以了,我们将会形成一个:

adjacentList<src, vector<dest>>

Traverse 图的方法:

方法1: DFS

这是一种相对比较标准的有序图的遍历解法。核心思想就是选定一个出发点,一路 traverse 直到到底(在我们的场景下就是最后一个最高阶的课程),然后再退回寻找其他路径,直到所有图都被覆盖。

在使用 DFS 进行遍历的时候,我们可以用一些小 tricks 来减少重复的遍历:

  • 利用 全局bool,如果一次遍历出现循环,则全局可能性为 false,不需要再遍历了
  • 利用“染色”的方法,没有被处理的点是白色,当前一轮 dfs 正在处理的点为灰色,而 dfs 结束以后的点处理为灰色
    • 我们进行起点选择的时候,只选择还是白色的点作为起点
    • 我们在遍历途中,如果发现我们将要去的点为灰色,这说明我们这次遍历遇到了循环,因为灰色意味着他和我们是同一组遍历被发现的,这个时候可以全局停止遍历了
    • 当一个点完成遍历以后,我们把他标记为黑色,并且放入已遍历的数组中
    • 因为DFS,一定是最高级的,也就是图中最后一个被遍历到的点先被放进数组,所以我们在输出结果的时候,将已遍历的结果倒转一下输出即可
  • Runtime O(V+E) 我们遍历了所有的 node 和 edge 各一次
  • Space O(V+E) 我们存储的也是所有的 node 和 他们的 edge

方法2: Indegree map 和 queue

这种方法是一种我们比较好想的方法,来源于我们日常生活中的思维模式:

在考虑一节课能不能上的时候,我们通常会考虑他的 prerequisite 有没有上完,而缺了几节 prerequisite,就意味着我们和这节课还有多少节课的差距

如果这个课没有任何依赖的话,我们就可以直接上这门课。

这个特性刚好可以图论中的 degree 特性连接起来。在一个有序图中,一个点的 degree 可以被表示为有多少个点以他为目标。在我们写 adj List 的时候,我们就可以同时记录这个点的 indegree,每当我们记录到一个 pair 的时候,这个 pair 的 dest 对应的点的 indegree 就要 + 1

而在我们进行遍历的时候,我们先将所有 indegree 为 0 的点放入 queue,假装我们是要上这些课,然后在不断的 pop 的过程中,就好似我们上完了一节节课以后,对应的其他课的 prerequisite 就减少了,也离我们更近了,所以对应这节课的所有 adj 的邻居 indegree 都 -1。而如果有新的课 indegree 变成  0 以后,意味着我们又可以上这门课了,我们就把他放入到 queue 中。一直反复直到我们的 queue 为空,无课可上了。

  • Runtime:O(V+E)
  • Space: O(V+E)

Note: 两种方法在 speed 和 space 上是相同量级的,但是因为 DFS 要使用 recursion 来进行实现,无论是内存的占用还是效率都是比不上仅使用循环的第二种方法的。实测第二种方法也是跑的相对更快一些,也更被我们的思维所接受。

题目代码:

方法1(DFS):

class Solution {
public:
    int WHITE = 1;
    int GRAY = 2;
    int BLACK = 3;
    
    void DFS(unordered_map<int, vector<int>> adjacentList, vector<int>& color, bool& isPossible, vector<int>& topologicalOrder, int i) {
        //break the research if it is impossible
        if(!isPossible)
            return;
        
        //the current process indicator, if we meet another gray when we want to traverse --> loop
        color[i] = GRAY;
        for(int node: adjacentList[i]){
            if(color[node] == WHITE){
                //a new node we can visit and try
                DFS(adjacentList, color, isPossible, topologicalOrder, node);
            }else if(color[node] == GRAY){
                //we encounter a loop, making the whole process not possible
                isPossible = false;
            }
        }
        
        //finish traverse this node, make it black, marked as visited and settled
        color[i] = BLACK;
        topologicalOrder.push_back(i);
    }
    
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        //create adjacent list and the topological color scheme
        unordered_map<int, vector<int>> adjacentList;
        vector<int> color(numCourses, WHITE);
        bool isPossible = true;
        vector<int> topologicalOrder;
        
        //fill out the adjacent List
        for(int i = 0; i < prerequisites.size(); i++){
            adjacentList[prerequisites[i][1]].push_back(prerequisites[i][0]);
        }
        
        //iterate every possible, non visited node using DFS
        for(int i = 0; i < numCourses; i++){
            if(!isPossible) 
                break;
            if(color[i] == WHITE)
                DFS(adjacentList, color, isPossible, topologicalOrder, i);
        }
        
        //reverse the topological order if needed:
        vector<int> order;
        if(isPossible){
            order.resize(numCourses);
            for(int i = 0; i < numCourses; i++){
                order[i] = topologicalOrder[numCourses-i-1];
            }
        }
        return order;
    }
};

方法2(Queue):

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        bool isPossible = true;
        vector<int> inDegree(numCourses, 0);
        map<int, vector<int>> adjList;
        vector<int> res;

        // Create the adjacency list representation of the graph.
        for (vector<int> relation : prerequisites) {
            int dest = relation[0];
            int src = relation[1];
            adjList[src].push_back(dest); // connect the neighbors
            inDegree[dest] += 1;          //since there is one more node to reach him
        }

        //first, we push every node with 0 indegree into the queue, since they are free to start:
        //we keep remove the nodes out of the graph, decresing their neighbours degrees as if 
        //we are finish one course and move to another. 
        queue<int> zeroDegree;
        for(int i = 0; i < numCourses; i++){
            if(inDegree[i] == 0){
                zeroDegree.push(i);
            }
        }
        while(!zeroDegree.empty()){
            int current = zeroDegree.front();
            zeroDegree.pop();
            res.emplace_back(current);
            
            for(int course: adjList[current]){
                inDegree[course] -= 1;
                //if there is a free elective rn, we push to the queue
                if(inDegree[course] == 0){
                    zeroDegree.push(course);
                }
            }
        }
        
        if(res.size() == numCourses){
            return res;
        }
        return vector<int>();
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2135645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kAFL部署、使用与原理分析

文章目录 前言1、概述1.1、工作原理1.2、工作流程1.2.1、部署kAFL1.2.2、准备工作1.2.2.1、准备主机代理内核1.2.2.2、准备待Fuzz目标1.2.2.3、配置待Fuzz目标1.2.2.4、配置kAFL组件 1.2.3、Fuzz测试1.2.3.1、获取配置信息1.2.3.2、准备工作目录1.2.3.3、复制种子文件1.2.3.4、…

大顶堆+动态规划+二分

前言&#xff1a;我们这一题需要分类讨论 对于我们左边和右边的我们需要预处理 有点类似反悔堆的做法&#xff0c;得出i之前取出 m 个元素代价最小&#xff0c;并且这个代价一定是递减的&#xff08;可以推导一下&#xff09; 题目地址 #include<bits/stdc.h> using name…

Docker 华为云镜像加速器配置

​​ 操作说明 1. 安装/升级容器引擎客户端 推荐安装1.11.2以上版本的容器引擎客户端 2. 加速器地址 访问华为云容器镜像服务&#xff1a;https://console.huaweicloud.com/swr/ 获取加速器地址 https://xxxxxxxxx.mirror.swr.myhuaweicloud.com3. 配置镜像加速器 针对…

c语言快递小项目

struct pack_head{ int fd;//通信的文件描述符 unsigned char type; //消息类型 unsigned char usertype; //用户类型&#xff1a;1&#xff1a;用户 2&#xff1a;快递员 char name[32]; //用户名 char paaswd[32]; //密码 char buf[32]; //调试…

抗金属RFID标签如何提升资产管理效率

在资产管理中&#xff0c;金属表面的设备和资产对传统RFID标签来说是一大挑战。为了解决这一问题&#xff0c;企业开始广泛采用抗金属RFID标签&#xff0c;以确保在金属环境下也能高效地进行资产跟踪与管理。 抗金属RFID标签的应用场景 抗金属RFID标签是一种专门设计用于金属…

如何让Windows控制台窗口不接受鼠标点击(禁用鼠标输入)

一、简述 在我们编写控制台应用程序时&#xff0c;默认情况下程序的打印输出会在控制台窗口中进行显示&#xff0c;我们在写服务功能时在窗口中会不断打印消息输出&#xff0c;这个时候如果使用鼠标点击了控制台窗口&#xff0c;会阻塞程序的继续运行&#xff0c;导致我们的程…

【Unity】在Unity 3D中使用Spine开发2D动画

文章目录 内容概括前言下载安装 Spine Pro导入Unity插件Spine动画导入Unity使用展现动画效果展现 内容概括 本文主要讲解 Spine Pro 免&#xff08;破&#xff09;费&#xff08;解&#xff09;版的安装&#xff0c;以及如何将动画导入到Unity中使用。 前言 通常要用 Spine …

Hadoop林子雨安装

文章目录 hadoop安装教程注意事项&#xff1a; hadoop安装教程 链接: 安装教程 注意事项&#xff1a; 可以先安装ububtu增强功能&#xff0c;完成共享粘贴板和共享文件夹 ubuntu增强功能 2.这里就可以使用共享文件夹 或者在虚拟机浏览器&#xff0c;用 微信文件传输助手 传文…

医学数据分析实训 项目二 数据预处理预备知识(数据标准化处理,数据离差标准化处理,数据二值化处理,独热编码处理,数据PCA降维处理)

文章目录 数据预处理预备知识任务一 数据标准化处理1. 数据准备2. 数据标准化 任务二 数据离差标准化处理任务三 数据二值化处理任务五 独热编码处理对数据进行“离散化处理”&#xff08;装箱&#xff09;将已经装箱的数据进行OneHotEncoder独热编码 任务六 数据PCA降维处理1.…

ingress对外服务

目录 ingress概念 安装ingress ingress-nginx暴露服务的方式 1. DeploymentLoadBalncer 2. DaemonSetHostNetworknodeSelector ​编辑 3.deploymentnodePort ​编辑 ingress-nginx的deploymentnodePorthttps部署 1.创建ssl的证书 2.ingressnginx ingress-nginx的权…

最强AI照片说话Windows一体包下载地址,口型合成音频驱动图片,免安装,下载即用

照片数字一键整合包&#xff1a;点击下载 一键安装包&#xff0c;简单一键启动&#xff0c;即刻使用&#xff0c;秒级体验。 目前效果最好的音频驱动图片说话的软件&#xff0c;比sadtalker、MuseTalk更清晰&#xff0c;效果更好&#xff0c;可以作为DID heygen的开源平替。原…

Vue安装及环境配置【图解版】

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 Facts speak louder than words&#xff01; 目录 一.node.js的安装…

C语言算法

大纲 算法复杂度 排序算法 经典算法

如何使用ssm实现物流配送人员车辆调度管理系统的设计与实现+vue

TOC ssm618物流配送人员车辆调度管理系统的设计与实现vue 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思…

react和vue区别以及为什么会说react适合大型项目

都说react适合做大型项目&#xff0c;但是什么是大型项目呢。 什么是大型项目 这个所谓的大项目应该是指 多部门&#xff0c;多项目协作。而并不是页面量和工作日&#xff0c;对于大公司&#xff0c;协作所带来的成本&#xff0c;效率问题才是问题 为什么会说react要更适合大型…

开源waf牛了个b之长亭雷池

雷池官网&#xff1a; https://waf-ce.chaitin.cn/

IVF 视频文件格式

IVF IVF有两种定义&#xff0c;一种是 Intel创建&#xff0c;用于封装其Indeo编解码器。Indeo是一系列视频编解码器&#xff0c;由英特尔在1990年代开发&#xff0c;主要用于视频游戏和早期的互联网视频流&#xff1b;Indeo编解码器以其高压缩率和良好的视频质量而闻名&#x…

三好夫人 | 茶香月饼甜  浓情赏月圆

在这个金风送爽、丹桂飘香的中秋佳节&#xff0c;家家户户都沉浸在团圆与温馨的氛围之中。月饼&#xff0c;作为中秋的传统美食&#xff0c;承载着无数人对家的思念与美好祝愿。而今&#xff0c;当传统遇见创新&#xff0c;“三好夫人”——一个以男士滋补茶闻名遐迩的品牌&…

AI做梦,探索并还原你的梦

本文由 ChatMoney团队出品 作为一个爱幻想爱做白日梦的 i人&#xff0c;我常常就在想&#xff0c;什么时候能利用Al来帮助我找回一些被遗忘的、或者模糊不清的记忆? 有没有可能进入别人的梦境里瞧一瞧? 为什么世界上还有这么多的冲突和摩擦? 是不是因为人与人之间能够达到的…

OpenCV GUI常用函数详解

在OpenCV的High_level GUI模组中有很多GUI函数&#xff0c;下面介绍几个常用的函数。 图像显示窗口相关函数 生成图像显示窗口函数nameWindow() nameWindow()函数的原型如下&#xff1a; 函数用以创建一个给定名的图像显示窗口&#xff08;后面简单叫做图像窗口&#xff09;…