时间调度问题的千层套路

2024-01-26 11:20

本文主要是介绍时间调度问题的千层套路,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

后台回复进群一起刷力扣

点击卡片可搜索关键词????

读完本文,可以去力扣解决如下题目:

252.会议室(Easy

253.会议室II(Medium

之前面试,被问到一道非常经典且非常实用的算法题目:会议室安排问题。

力扣上类似的问题是会员题目,你可能没办法做,但对于这种经典的算法题,掌握思路还是必要的。

先说下题目,给你输入若干形如[begin, end]的区间,代表若干会议的开始时间和结束时间,请你计算至少需要申请多少间会议室。

函数签名如下:

// 返回需要申请的会议室数量
int minMeetingRooms(int[][] meetings);

比如给你输入meetings = [[0,30],[5,10],[15,20]],算法应该返回 2,因为后两个会议和第一个会议时间是冲突的,至少申请两个会议室才能让所有会议顺利进行。

如果会议之间的时间有重叠,那就得额外申请会议室来开会,想求至少需要多少间会议室,就是让你计算同一时刻最多有多少会议在同时进行。

换句话说,如果把每个会议的起止时间看做一个线段区间,那么题目就是让你求最多有几个重叠区间,仅此而已。

对于这种时间安排的问题,本质上讲就是区间调度问题,十有八九得排序,然后找规律来解决。

题目延伸

我们之前写过很多区间调度相关的文章,这里就顺便帮大家梳理一下这类问题的思路:

第一个场景,假设现在只有一个会议室,还有若干会议,你如何将尽可能多的会议安排到这个会议室里?

这个问题需要将这些会议(区间)按结束时间(右端点)排序,然后进行处理,详见前文 贪心算法做时间管理。

第二个场景,给你若干较短的视频片段,和一个较长的视频片段,请你从较短的片段中尽可能少地挑出一些片段,拼接出较长的这个片段。

这个问题需要将这些视频片段(区间)按开始时间(左端点)排序,然后进行处理,详见前文 剪视频剪出一个贪心算法。

第三个场景,给你若干区间,其中可能有些区间比较短,被其他区间完全覆盖住了,请你删除这些被覆盖的区间。

这个问题需要将这些区间按左端点排序,然后就能找到并删除那些被完全覆盖的区间了,详见前文 删除覆盖区间。

第四个场景,给你若干区间,请你将所有有重叠部分的区间进行合并。

这个问题需要将这些区间按左端点排序,方便找出存在重叠的区间,详见前文 合并重叠区间。

第五个场景,有两个部门同时预约了同一个会议室的若干时间段,请你计算会议室的冲突时段。

这个问题就是给你两组区间列表,请你找出这两组区间的交集,这需要你将这些区间按左端点排序,详见前文 区间交集问题。

第六个场景,假设现在只有一个会议室,还有若干会议,如何安排会议才能使这个会议室的闲置时间最少?

这个问题需要动动脑筋,说白了这就是个 0-1 背包问题的变形:

会议室可以看做一个背包,每个会议可以看做一个物品,物品的价值就是会议的时长,请问你如何选择物品(会议)才能最大化背包中的价值(会议室的使用时长)?

当然,这里背包的约束不是一个最大重量,而是各个物品(会议)不能互相冲突。把各个会议按照结束时间进行排序,然后参考前文 0-1 背包问题详解 的思路即可解决,等我以后有机会可以写一写这个问题。

第七个场景,就是本文想讲的场景,给你若干会议,让你合理申请会议室。

好了,举例了这么多,来看看今天的这个问题如何解决。

题目分析

重复一下题目的本质:

给你输入若干时间区间,让你计算同一时刻「最多」有几个区间重叠

题目的关键点在于,给你任意一个时刻,你是否能够说出这个时刻有几个会议在同时进行?

如果可以做到,那我遍历所有的时刻,找个最大值,就是需要申请的会议室数量。

有没有一种数据结构或者算法,给我输入若干区间,我能知道每个位置有多少个区间重叠?

老读者肯定可以联想到之前说过的一个算法技巧:差分数组技巧。

把时间线想象成一个初始值为 0 的数组,每个时间区间[i, j]就相当于一个子数组,这个时间区间有一个会议,那我就把这个子数组中的元素都加一。

最后,每个时刻有几个会议我不就知道了吗?我遍历整个数组,不就知道至少需要几间会议室了吗?

举例来说,如果输入meetings = [[0,30],[5,10],[15,20]],那么我们就给数组中[0,30],[5,10],[15,20]这几个索引区间分别加一,最后遍历数组,求个最大值就行了。

还记得吗,差分数组技巧可以在 O(1) 时间对整个区间的元素进行加减,所以可以拿来解决这道题。

不过,这个解法的效率不算高,所以我这里不准备具体写差分数组的解法,参照 差分数组技巧 的原理,有兴趣的读者可以自己尝试去实现。

基于差分数组的思路,我们可以推导出一种更高效,更优雅的解法

我们首先把这些会议的时间区间进行投影:

红色的点代表每个会议的开始时间点,绿色的点代表每个会议的结束时间点。

现在假想有一条带着计数器的线,在时间线上从左至右进行扫描,每遇到红色的点,计数器count加一,每遇到绿色的点,计数器count减一:

这样一来,每个时刻有多少个会议在同时进行,就是计数器count的值,count的最大值,就是需要申请的会议室数量

对差分数组技巧熟悉的读者一眼就能看出来了,这个扫描线其实就是差分数组的遍历过程,所以我们说这是差分数组技巧衍生出来的解法。

代码实现

那么,如何写代码实现这个扫描的过程呢?

首先,对区间进行投影,就相当于对每个区间的起点和终点分别进行排序:

int minMeetingRooms(int[][] meetings) {int n = meetings.length;int[] begin = new int[n];int[] end = new int[n];// 把左端点和右端点单独拿出来for(int i = 0; i < n; i++) {begin[i] = meetings[i][0];end[i] = meetings[i][1];}// 排序后就是图中的红点Arrays.sort(begin);// 排序后就是图中的绿点Arrays.sort(end);// ...
}

然后就简单了,扫描线从左向右前进,遇到红点就对计数器加一,遇到绿点就对计数器减一,计数器count的最大值就是答案:

int minMeetingRooms(int[][] meetings) {int n = meetings.length;int[] begin = new int[n];int[] end = new int[n];for(int i = 0; i < n; i++) {begin[i] = meetings[i][0];end[i] = meetings[i][1];}Arrays.sort(begin);Arrays.sort(end);// 扫描过程中的计数器int count = 0;// 双指针技巧int res = 0, i = 0, j = 0;while (i < n && j < n) {if (begin[i] < end[j]) {// 扫描到一个红点count++;i++;} else {// 扫描到一个绿点count--;j++;}// 记录扫描过程中的最大值res = Math.max(res, count);}return res;
}

这里使用的是 双指针技巧,你可以认为指针 i 就是那根扫描线,根据i, j的相对位置就可以模拟扫描线前进的过程。

至此,这道题就做完了。当然,这个题目也可以变形,比如给你若干会议,问你k个会议室够不够用,其实你套用本文的解法代码,也可以很轻松解决。

接下来可阅读:

区间问题系列合集

_____________

学好算法靠套路,认准 labuladong,知乎、B站账号同名。公众号后台回复「微信」可加我好友,回复「目录」可获取文章分类:

这篇关于时间调度问题的千层套路的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/646635

相关文章

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分

解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题

《解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题》在Spring开发中,@Autowired注解常用于实现依赖注入,它可以应用于类的属性、构造器或setter方法上,然... 目录1. 为什么 @Autowired 在属性上被警告?1.1 隐式依赖注入1.2 IDE 的警告:

解决java.lang.NullPointerException问题(空指针异常)

《解决java.lang.NullPointerException问题(空指针异常)》本文详细介绍了Java中的NullPointerException异常及其常见原因,包括对象引用为null、数组元... 目录Java.lang.NullPointerException(空指针异常)NullPointer

Android开发中gradle下载缓慢的问题级解决方法

《Android开发中gradle下载缓慢的问题级解决方法》本文介绍了解决Android开发中Gradle下载缓慢问题的几种方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、网络环境优化二、Gradle版本与配置优化三、其他优化措施针对android开发中Gradle下载缓慢的问

关于Nginx跨域问题及解决方案(CORS)

《关于Nginx跨域问题及解决方案(CORS)》文章主要介绍了跨域资源共享(CORS)机制及其在现代Web开发中的重要性,通过Nginx,可以简单地解决跨域问题,适合新手学习和应用,文章详细讲解了CO... 目录一、概述二、什么是 CORS?三、常见的跨域场景四、Nginx 如何解决 CORS 问题?五、基

MySQL安装时initializing database失败的问题解决

《MySQL安装时initializingdatabase失败的问题解决》本文主要介绍了MySQL安装时initializingdatabase失败的问题解决,文中通过图文介绍的非常详细,对大家的学... 目录问题页面:解决方法:问题页面:解决方法:1.勾选红框中的选项:2.将下图红框中全部改为英

springboot的调度服务与异步服务使用详解

《springboot的调度服务与异步服务使用详解》本文主要介绍了Java的ScheduledExecutorService接口和SpringBoot中如何使用调度线程池,包括核心参数、创建方式、自定... 目录1.调度服务1.1.JDK之ScheduledExecutorService1.2.spring

Nginx启动失败:端口80被占用问题的解决方案

《Nginx启动失败:端口80被占用问题的解决方案》在Linux服务器上部署Nginx时,可能会遇到Nginx启动失败的情况,尤其是错误提示bind()to0.0.0.0:80failed,这种问题通... 目录引言问题描述问题分析解决方案1. 检查占用端口 80 的进程使用 netstat 命令使用 ss

mybatis和mybatis-plus设置值为null不起作用问题及解决

《mybatis和mybatis-plus设置值为null不起作用问题及解决》Mybatis-Plus的FieldStrategy主要用于控制新增、更新和查询时对空值的处理策略,通过配置不同的策略类型... 目录MyBATis-plusFieldStrategy作用FieldStrategy类型每种策略的作

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多