leetcode 扫描线专题 06-leetcode.253 meeting room ii 力扣.253 会议室 II

扫描线专题

leetcode 数组专题 06-扫描线算法(Sweep Line Algorithm)

leetcode 数组专题 06-leetcode.218 the-skyline-problem 力扣.218 天际线问题

leetcode 数组专题 06-leetcode.252 meeting room 力扣.252 会议室

leetcode 数组专题 06-leetcode.253 meeting room ii 力扣.253 会议室 II

题目

给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,返回 所需会议室的最小数量 。

示例 1:

输入:intervals = [[0,30],[5,10],[15,20]]
输出:2

示例 2:

输入:intervals = [[7,10],[2,4]]
输出:1

提示:

1 <= intervals.length <= 10^4

0 <= starti < endi <= 10^6

整体思路

一般这种区间的题目,常见的有下面的解决方案:

  1. 暴力

  2. 排序

  3. 扫描线

  4. 优先队列

不过这个感觉暴力不是很实用,不排序的话时间顺序无法确定,会导致结果不正确。

v1-排序

思路

会议的开始/结束时间,分别放在 2 个数组。然后对比。

如果不使用优先队列(最小堆),而是只通过 排序直接对比 来实现最小会议室数,思路可以是这样的:

  1. 排序:首先按照会议的开始时间排序,然后再按结束时间排序。

  2. 模拟会议室的分配

    • 在同一时刻,检查所有已安排的会议室是否有空余的。具体来说,当一个会议开始时,检查是否有会议已经结束(通过结束时间来判断)。如果有空余的会议室,就可以复用该会议室,否则需要额外增加一个会议室。

    • 通过维护一个结束时间列表来跟踪当前所有会议的结束时间。

  3. 直接比较

    • 对于每个会议,通过扫描结束时间列表判断是否有会议结束。如果有,则将其结束时间更新为当前会议的结束时间;如果没有结束的会议,说明需要增加一个新的会议室。

代码实现:

我们不使用优先队列,直接通过两个数组来实现。

  1. 排序会议的开始时间
  2. 通过一个循环遍历会议,来模拟每次会议的安排过程。

代码实现:

import java.util.*;public class MeetingRoomsII {public static int minMeetingRooms(int[][] intervals) {if (intervals.length == 0) {return 0;}int n = intervals.length;// Step 1: Create two arrays to store the start and end timesint[] startTimes = new int[n];int[] endTimes = new int[n];for (int i = 0; i < n; i++) {startTimes[i] = intervals[i][0];endTimes[i] = intervals[i][1];}// Step 2: Sort both start and end timesArrays.sort(startTimes);Arrays.sort(endTimes);int roomCount = 0;int endIndex = 0;// Step 3: Process each meeting one by onefor (int i = 0; i < n; i++) {// If the current meeting starts after or when a meeting ends, reuse the room// 如果这一次开始时间在上一次的结束之后,则可以复用房间if (startTimes[i] >= endTimes[endIndex]) {// Reuse the room: move the endIndex to the next meetingendIndex++;} else {// If no room is available, we need a new oneroomCount++;}}// The room count will be the number of rooms we needreturn roomCount + 1; // We need at least one room}
}

代码解释:

  1. 排序

    • 我们将所有的会议的开始时间和结束时间分别存入 startTimesendTimes 数组,并对这两个数组进行排序。这样,我们可以确保在处理会议时,始终按顺序处理会议的开始和结束时间。
  2. 直接对比

    • 我们使用一个 endIndex 变量来跟踪当前最早结束的会议的结束时间。
    • 对于每个会议(按开始时间顺序),我们判断它是否可以复用当前已经结束的会议室:即,当前会议的开始时间是否大于等于最早结束会议的结束时间。如果可以复用,我们将 endIndex 移动到下一个结束的会议;如果不能复用,则说明需要一个新的会议室。
    • roomCount 用于记录会议室的数量。
  3. 返回值

    • 最终返回 roomCount + 1,因为我们至少需要一个会议室来安排第一个会议。

v2-扫描线

思路

使用 扫描线算法 来解决 Leetcode 253 - 会议室 II 的问题,是一种非常巧妙且高效的方法。

这个方法的核心思想是将所有的会议事件(开始和结束)转化为事件点,然后按时间顺序处理这些事件,模拟会议室的使用情况。

思路:

  1. 事件拆分:对于每个会议 interval[i] = [starti, endi],我们将其拆解为两个事件:

    • 开始事件:表示一个新的会议室被占用。
    • 结束事件:表示一个会议室被释放。
  2. 排序事件:所有的事件按时间进行排序。需要注意:

    • 开始事件:在相同的时间点,会议的开始需要比结束事件优先处理。这是因为,如果两个会议同时开始和结束,我们希望优先安排新的会议,而不是释放会议室。
  3. 扫描处理事件:扫描这些排序后的事件,使用一个变量来记录当前同时进行的会议室数量,并更新最大会议室数量。

    • 遇到开始事件,会议室数增加。
    • 遇到结束事件,会议室数减少。
  4. 最大会议室数:扫描过程中维护一个 maxRooms 变量来记录需要的最大会议室数量。

详细步骤:

  1. 将每个会议拆分成开始和结束两个事件。
  2. 按照事件的时间进行排序,如果时间相同,则结束事件优先。
  3. 扫描所有事件,计算同时进行的会议数量,得到最大值。

扫描线算法实现:

import java.util.*;public class MeetingRoomsII {public static int minMeetingRooms(int[][] intervals) {if (intervals.length == 0) {return 0;}// Step 1: Create a list to store all events (start and end times)List<int[]> events = new ArrayList<>();for (int[] interval : intervals) {// Add start eventevents.add(new int[]{interval[0], 1});// Add end eventevents.add(new int[]{interval[1], -1});}// Step 2: Sort the events:// - First by time.// - If two events have the same time, prioritize end event (-1) over start event (1).events.sort((a, b) -> a[0] == b[0] ? Integer.compare(a[1], b[1]) : Integer.compare(a[0], b[0]));// Step 3: Scan the events and maintain the number of meeting rooms in use.int maxRooms = 0;int roomsInUse = 0;for (int[] event : events) {// Update the number of rooms in useroomsInUse += event[1];// Update the maximum number of rooms neededmaxRooms = Math.max(maxRooms, roomsInUse);}return maxRooms;}
}

代码解释:

  1. 事件转换

    • 对于每个会议 interval[i] = [starti, endi],我们生成两个事件:一个是开始事件 starti,表示需要一个会议室;另一个是结束事件 endi,表示释放一个会议室。
    • 我们用 1 表示开始事件,用 -1 表示结束事件,这样在事件排序时可以通过 -1 优先处理结束事件,确保结束会议时会议室被释放。
  2. 排序事件

    • 首先根据时间排序。如果时间相同,我们优先处理结束事件(-1),因为如果两个会议同时开始和结束,我们希望先释放会议室,再开始新的会议。
  3. 扫描事件

    • 初始化 roomsInUse 变量来记录当前正在使用的会议室数量。
    • 遍历所有排序后的事件,每次遇到开始事件(1),就增加 roomsInUse;每次遇到结束事件(-1),就减少 roomsInUse
    • 同时,维护一个 maxRooms 变量来记录 roomsInUse 的最大值,即最大会议室数量。

时间复杂度:

  • 事件拆分:我们有 N 个会议,每个会议生成两个事件,因此总共有 2N 个事件,时间复杂度是 O(N)。
  • 排序:排序事件的时间复杂度是 O(2N log(2N)),也就是 O(N log N)。
  • 扫描:扫描事件的时间复杂度是 O(2N),也就是 O(N)。

因此,总的时间复杂度是 O(N log N),主要由排序操作主导。

空间复杂度:

  • 需要一个大小为 2N 的列表来存储事件,空间复杂度是 O(N)

小结

感觉扫描线挺有趣的,下一个系列我们就来学习一下这个扫描线专题算法。

V3-优先级队列(最小堆)

思路:

最小堆可以用来模拟会议室的动态分配,我们将会议的结束时间视为会议室的 "可用时间",利用堆来维护当前所有已分配的会议室的结束时间。

  1. 排序会议的开始时间:首先,我们将所有会议按照 开始时间 排序,这样可以按顺序处理会议。

  2. 使用最小堆:我们使用一个最小堆来维护所有已分配会议室的结束时间。堆顶元素表示最早结束的会议。

    • 如果当前会议的开始时间大于或等于堆顶元素的结束时间,说明当前会议可以复用堆顶的会议室。此时,弹出堆顶元素(即释放一个会议室),并将当前会议的结束时间加入堆中。

    • 如果当前会议的开始时间小于堆顶元素的结束时间,说明需要新分配一个会议室,此时将当前会议的结束时间加入堆中。

  3. 堆的大小:堆的大小即为当前所需的会议室数,最终堆的大小就是我们所需的最小会议室数。

代码

import java.util.*;public class MeetingRoomsII {public static int minMeetingRooms(int[][] intervals) {if (intervals.length == 0) {return 0;}// Step 1: Sort the meetings in increasing order of start timeArrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));// Step 2: Create a min-heap to track the end times of meetingsPriorityQueue<Integer> minHeap = new PriorityQueue<>();// Step 3: Add the first meeting's end time to the heapminHeap.offer(intervals[0][1]);// Step 4: For all the remaining meetingsfor (int i = 1; i < intervals.length; i++) {// If the meeting can reuse the room, pop the earliest ending meetingif (intervals[i][0] >= minHeap.peek()) {minHeap.poll();}// Add the current meeting's end time to the heapminHeap.offer(intervals[i][1]);}// Step 5: The size of the heap is the minimum number of meeting rooms requiredreturn minHeap.size();}
}

代码解释

  1. 排序会议开始时间

    • 我们首先按照会议的 开始时间 对会议进行排序。这样可以确保我们处理会议时,总是按开始时间的顺序来安排会议室。
  2. 最小堆(优先队列)

    • 使用一个最小堆来维护已分配的会议室的结束时间。最小堆的堆顶始终是当前最早结束的会议。
    • 每当一个新会议到来时,我们检查当前会议的开始时间是否大于等于堆顶的结束时间,如果是,则可以复用一个会议室,弹出堆顶并将新会议的结束时间加入堆中。如果不能复用,则需要一个新的会议室,将新会议的结束时间加入堆中。
  3. 堆的大小

    • 最后,堆的大小即为所需的最小会议室数,因为堆的每个元素表示一个正在使用的会议室,堆的大小就是会议室的数量。

时间复杂度:

  • 排序:排序会议的开始时间的时间复杂度是 O(N log N),其中 N 是会议的数量。
  • 堆操作:我们需要遍历所有会议,对于每个会议执行 polloffer 操作,这两个操作的时间复杂度是 O(log N)。
  • 因此,总的时间复杂度是 O(N log N),主要由排序和堆操作主导。

空间复杂度:

  • 空间复杂度为 O(N),用于存储堆中的会议室结束时间。最坏情况下,所有会议都需要一个单独的会议室,堆的大小为 N。

本题中最小堆到底解决了什么问题?

  1. 会议室的结束时间是关键

每个会议都有一个 结束时间,我们只关心是否可以复用已分配的会议室。

复用的前提是当前会议的 开始时间 要晚于或等于某个会议室的 结束时间

  • 如果当前会议的 开始时间 start[i] 大于等于某个会议室的 结束时间 end[j],说明该会议室在当前时刻空闲,可以被复用。
  • 否则,我们就需要为当前会议分配一个新的会议室。
  1. 为什么使用最小堆?

最小堆 的特点是能够在 O(log N) 时间复杂度内找出堆顶元素(最小值)。

在本题中,我们使用最小堆来保持当前正在使用的会议室的结束时间,并始终保持堆顶是最早结束的会议室。

这样可以确保我们每次都能优先复用最早结束的会议室。

如何利用最小堆来解决问题?

最小堆的关键作用是帮助我们高效地跟踪并更新最早结束的会议室:

  • 添加会议室:每当我们遇到一个新的会议时,我们将当前会议的结束时间加入堆中。

  • 检查复用:每次处理一个新的会议时,我们检查堆顶元素(即最早结束的会议室的结束时间):

    • 如果当前会议的开始时间大于或等于堆顶的结束时间,则说明我们可以复用这个会议室,弹出堆顶元素,并将新的会议结束时间加入堆。
    • 如果不能复用(即当前会议的开始时间小于堆顶的结束时间),则我们需要为当前会议分配一个新的会议室,将新会议的结束时间加入堆中。
4. 核心操作
  • 堆顶元素的含义:堆顶元素表示当前最早结束的会议室的结束时间。

通过弹出堆顶元素,我们可以将该会议室释放出来,供下一次会议复用。

  • 堆的大小:堆的大小即为当前所需要的会议室数,最终堆的大小即为最小会议室数量。

小结

这一题我们的暴力算法是不太行的通的。

整体下来就是几个主流思路:

1)排序+逻辑比较

2)排序+扫描线

3)排序+优先级队列

后面两个为我们引入了新的知识点:扫描线+优先级队列。

我们后面的专题就一起来学习一下。

开源地址

为了便于大家学习,所有实现均已开源。欢迎 fork + star~

https://github.com/houbb/leetcode

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

L11.【LeetCode笔记】有效的括号

目录 1.题目 2.分析 理解题意 解决方法 草稿代码 ​编辑 逐一排错 1.当字符串为"["时,分析代码 2.当字符串为"()]"时,分析代码 正确代码(isValid函数部分) 提交结果 3.代码优化 1.题目 https://leetcode.cn/problems/valid-parentheses/descri…

paddle表格识别数据制作

数据格式 其中主要数据有两个一个表格结构的检测框&#xff0c;一个是tokens&#xff0c;注意的地方是 1、只能使用双引号&#xff0c;单引号不行 2、使用带引号的地方是tokens里面 "<tr>", "<td", " colspan2", ">",&quo…

深度学习中的Pixel Shuffle和Pixel Unshuffle:图像超分辨率的秘密武器

在深度学习的计算机视觉任务中&#xff0c;提升图像分辨率和压缩特征图是重要需求。Pixel Shuffle和Pixel Unshuffle是在超分辨率、图像生成等任务中常用的操作&#xff0c;能够通过转换空间维度和通道维度来优化图像特征表示。本篇文章将深入介绍这两种操作的原理&#xff0c;…

阮一峰科技爱好者周刊(第 325 期)推荐工具:一个基于 Next.js 的博客和 CMS 系统

近期&#xff0c;阮一峰在科技爱好者周刊第 325 期中推荐了一款开源工具——ReactPress&#xff0c;ReactPress一个基于 Next.js 的博客和 CMS 系统&#xff0c;可查看 demo站点。&#xff08;fecommunity 投稿&#xff09; ReactPress&#xff1a;一款值得推荐的开源发布平台 …

Amazon Web Services (AWS)

一、Amazon Web Services (AWS)介绍 1、简介 2、产品 AWS 提供了各种云计算服务&#xff0c;包括 DynamoDB、S3、EC2、Lambda 等等。 登录aws后点击所有服务也可以看到amazon的所有服务&#xff1a; 3、免费试用产品 除了免费的Amazon Step Functions、Amazon Lambda&#…

rk3399开发环境使用Android 10初体验蓝牙功能

版本 日期 作者 变更表述 1.0 2024/11/10 于忠军 文档创建 零. 前言 由于Bluedroid的介绍文档有限&#xff0c;以及对Android的一些基本的知识需要了(Android 四大组件/AIDL/Framework/Binder机制/JNI/HIDL等)&#xff0c;加上需要掌握的语言包括Java/C/C等&#xff0…

Redis实战案例(黑马点评)

List item Redis实战案例&#xff08;黑马点评&#xff09; 一、短信登录 tomcat的运行原理&#xff1a; 当用户发起请求时&#xff0c;会访问tomcat注册的端口&#xff0c;任何程序想要运行&#xff0c;都需要有一个线程对当前端口号进行监听&#xff0c;当用户和tomcat连…

每行数据个数在变的二维数组的输出

#include<stdio.h> int main() {//定义四个一维数组int arr1[1] { 1 };int arr2[3] { 1,2,3 };int arr3[5] { 1,2,3,4,5 };int arr4[7] { 1,2,3,4,5,6,7 };//把四个一维数组放进一个二维数组int* arr[4] { arr1,arr2,arr3,arr4};//预先计算好每一个数组真实的长度in…

IPv6 NDP 记录

NDP&#xff08;Neighbor Discovery Protocol&#xff0c;邻居发现协议&#xff09; 是 IPv6 的一个关键协议&#xff0c;它组合了 IPv4 中的 ARP、ICMP 路由器发现和 ICMP 重定向等协议&#xff0c;并对它们作出了改进。该协议使用 ICMPv6 协议实现&#xff0c;作为 IPv6 的基…

MySQL数据库:SQL语言入门 【2】(学习笔记)

目录 2&#xff0c;DML —— 数据操作语言&#xff08;Data Manipulation Language&#xff09; &#xff08;1&#xff09;insert 增加 数据 &#xff08;2&#xff09;delete 删除 数据 truncate 删除表和数据&#xff0c;再创建一个新表 &#xff08;3&#xf…

第二十一周机器学习笔记:动手深度学习之——数据操作、数据预处理

第二十周周报 摘要Abstract一、动手深度学习1. 数据操作1.1 数据基本操作1.2 数据运算1.2.1 广播机制 1.3 索引和切片 2. 数据预处理 二、复习RNN与LSTM1. Recurrent Neural Network&#xff08;RNN&#xff0c;循环神经网络&#xff09;1.1 词汇vector的编码方式1.2 RNN的变形…

购物车demo全代码-对接支付宝沙箱环境

创建项目 vue create alipay-demoAlipayDemo.vue <template><div class"cart-container"><h2>商品列表</h2><table class"product-table"><tr><th>商品</th><th>价格</th><th>商品描…

【CANOE】【学习】【DecodeString】字节转为中文字符输出

系列文章目录 文章目录 系列文章目录前言一、DecodeString 转为中文字节输出二、代码举例1.代码Demo2.DecodeString 函数说明函数语法&#xff1a;参数说明&#xff1a;返回值&#xff1a;使用示例&#xff1a;示例代码&#xff1a; 说明&#xff1a; 前言 有时候使用的时候&a…

超全超详细使用SAM进行高效图像分割标注(GPU加速推理)

一、前言 &#x1f449; 在计算机视觉任务中&#xff0c;图像分割 是重要的基础工作&#xff0c;但人工标注往往耗时耗力。Meta推出的 SAM&#xff08;Segment Anything Model&#xff09;&#xff0c;大幅提升了分割效率和精度&#xff0c;让标注工作更加轻松。本篇博客将详细…

JavaEE 重要的API阅读

JavaEE API阅读 目的是为了应对学校考试&#xff0c;主要关注的是类的继承关系、抛出错误的类型、包名、包结构等等知识。此帖用于记录。 PageContext抽象类 包名及继承关系 继承自JspContext类。PageContext 实例提供对与某个 JSP 页⾯关联的所有名称空间的访问&#xff0…

【Python · PyTorch】卷积神经网络(基础概念)

【Python PyTorch】卷积神经网络 CNN&#xff08;基础概念&#xff09; 0. 生物学相似性1. 概念1.1 定义1.2 优势1.2.1 权重共享1.2.2 局部连接1.2.3 层次结构 1.3 结构1.4 数据预处理1.4.1 标签编码① One-Hot编码 / 独热编码② Word Embedding / 词嵌入 1.4.2 归一化① Min-…

Python爬虫----python爬虫基础

一、python爬虫基础-爬虫简介 1、现实生活中实际爬虫有哪些&#xff1f; 2、什么是网络爬虫&#xff1f; 3、什么是通用爬虫和聚焦爬虫&#xff1f; 4、为什么要用python写爬虫程序 5、环境和工具 二、python爬虫基础-http协议和chrome抓包工具 1、什么是http和https协议…

Python学习笔记(2)正则表达式

正则表达式是一个特殊的字符序列&#xff0c;它能帮助你方便的检查一个字符串是否与某种模式匹配。 在 Python 中&#xff0c;使用 re 模块提供的函数来处理正则表达式&#xff0c;允许你在字符串中进行模式匹配、搜索和替换操作。 1 正则表达式 正则表达式(Regular Expressi…

整数唯一分解定理

整数唯一分解定理&#xff0c;也称为算术基本定理&#xff0c;是由德国数学家高斯在其著作《算术研究》中首次提出的。本文回顾整数唯一分解定理以及对应的几个重要结论。 一、整数唯一分解定理 整数唯一分解定理&#xff0c;也称为算术基本定理&#xff0c;是数论中的一个重…

小版本大不同 | Navicat 17 新增 TiDB 功能

近日&#xff0c;Navicat 17 迎来了小版本更新。此次版本新增了对 PingCap 公司的 TiDB 开源分布式关系型数据库的支持&#xff0c;进一步拓展了 Navicat 的兼容边界。即日起&#xff0c;Navicat 17 所有用户可免费升级至最新版本&#xff0c;通过 Navicat 工具实现 TiDB 数据库…