面试经典150题——用最少数量的箭引爆气球

"The only person you are destined to become is the person you decide to be." - Ralph Waldo Emerson

scenery of mountain

1. 题目描述

2.  题目分析与解析

这个题目开始读题的时候是有点不好理解题意的,因此我先做个图让大家对于题意有更好更直观的理解再来分析题目。

比如对于这个测试用例:

image-20240301092542335

实际上points就是气球的宽度,可视化如下:

image-20240301093354544

而题目要求的就是引爆所有气球所必须射出的 最小 弓箭数 ,对于上述情况,那么就需要以下红色的两支箭:

image-20240301093746553

其中虚线框就是表示两支箭的可以横向移动的范围。第一支箭从【2,6】都可以射出从而击破两个气球,第二支箭从【10,12】都可以射出击破剩下的两个气球。

2.1 思路一

可以看出本题的实质就是在找能够涵盖所有气球的最小交集个数。那么我们首先应该想到的是排序,经过排序后,然后遍历每一个区间,查看当前区间能够附带击破的气球的个数,比如如下图:

image-20240301094647448

首先遍历第一个区间【1,6】,查看每一个位置能够附带击破的气球的个数,在数字1的位置,发现只能附带击破自己本身1个气球,在【2,4】发现能击破两个,但是在【5,6】(因为等于5就可以击破蓝色气球:满足  xstart ≤ x ≤ x``end,则该气球会被 引爆 ),所以我们肯定得将箭设置成能击破最多个数气球的位置,也就是击破三个气球,然后移除这些已经被击破的气球,继续寻找下一个区间,以此循环,直到没有剩余的气球。

代码思路

  1. 初始化两个变量,一个用来统计删除区间的位置,一个用来统计结果

  2. 按照区间的开始位置进行从小到大排序

  3. 从最小的区间开始,查看每一个数字射出箭能够击破的最大气球数以及击破气球的位置

    • 也就是判断某个数字能否被某些区间所包含,即是否大于等于区间的最小值且小于等于区间的最大值

    • 找到最多的能被包含的位置及相关区间,删除这些区间

  4. 从当前集合中取出下一个最小区间重复以上步骤直到集合为空

  5. 返回结果

但是理所当然的报了超时,因为我们尝试去遍历了每一个可能射出箭的位置

image-20240301103046577

2.2 思路二

根据上面的思路,其实我们的本质就是找每个区间和其余区间的最大交集个数。

而因为如果我们按照区间的开始位置从小到大进行排序,那么我们就需要根据下一个区间的开始位置是否小于等于当前遍历区间的结束位置,如下,假设现在遍历第一个蓝色区间:

image-20240301111734803

我们发现绿色的开始位置小于蓝色气球的结束位置,所以我们就可以断定他们俩有交集(因为左端点已经经过排序了的)。然后再看下一个区间也就是黄色气球,发现还是有交集,那么我们是不是就可以断定这一支箭一定就可以一串三呢?

答案是否定的,虽然对于上述图中是可以从x=6射出一只箭来打破这三个气球,然后从剩余气球的第一个位置继续遍历,但是如果是如下的情况呢?

image-20240301112249384

这时可以发现,虽然遍历的第一个蓝色气球和黄色绿色两个气球都相交,但是我们并不能用一只箭将他们全部射破,因为绿色和黄色并不像上面开始的图中给出的示例一样能够在 x=6处相交。

所以此时要注意,在判断完毕第一个蓝色气球和绿色气球之后,我们需要把结束位置更新,更新为 6,因为绿色气球必须要一只 x<=6 的箭来射穿它。

这时如果结束位置是6,我们再来判断黄色气球的开始位置和当前区间也就是 【1,6】是否相交,发现是不可以的,就可以断定需要一只箭来射穿前两个气球。然后我们再从黄色气球开始向后按照之前的步骤继续发出箭。

所以核心点就是: 我们一定要把结束位置更新为走过的气球中的最小值

所以根据以上性质,我们可以给出以下代码思路:

代码思路:

  1. 对气球区间进行升序排序

  2. 遍历每一个区间,查看当前区间的下一个区间的开始位置是否小于等于当前区间的结束位置

    • 如果满足,就将遍历的区间索引++,也就相当于移除了这个区间,然后更新右端点为两者的最小值。

  3. 如果走到某一个区间发现不满足上述条件,那么直接射出一只箭,然后遍历剩下的区间

小错误排查

但是按照以上思路求解发现了一个问题,就是对于该测试用例:

image-20240301115301619

发现解答错误,然后我debug了一下,发现按照从小到大排序是这样的:

image-20240301115243116

通过排查了一番,找到了原因如下:

在排序时,我是用如下代码:

        //1. 对气球区间进行升序排序Arrays.sort(points, (o1, o2) -> o1[0] - o2[0]);

该代码片段是使用一个自定义比较器对二维数组points进行排序的Java代码。这个比较器是一个lambda表达式,它比较两个区间的开始点o1[0]o2[0]

这个比较器通过计算两个开始点的差值o1[0] - o2[0]来决定排序顺序。在Java中,Comparator接口期望的返回值为负数、零或正数,分别表示第一个参数小于、等于或大于第二个参数。

但是因为测试用例中整数值接近Integer类型的极限,所以这个比较器的计算可能会导致整数溢出:

  • 如果o1[0]是一个非常大的正数,而o2[0]是一个负数,那么理论上o1[0]应该大于o2[0]

  • 但是如果o1[0]o2[0]的差值大于Integer.MAX_VALUE(即2147483647),那么计算结果将会溢出,导致返回一个负值。

  • 这会使得排序函数错误地认为o1[0]小于o2[0]

为了避免整数溢出,那么应该使用Integer.compare()方法来比较两个整数,它能正确处理溢出的情况。下面是修改后的代码:

Arrays.sort(points, (o1, o2) -> Integer.compare(o1[0], o2[0]));

使用Integer.compare()方法可以确保即使是接近整数极限的值,比较操作也会正确执行,返回正确的排序顺序。

经过修改后完美解决!

2.3 思路三

在思路二中,实际上是对思路一的优化,减少了一些不必要的判断,思路二需要不断更新右边界来确保正确的射出箭。那么我们可不可以不更新右边界呢?

因为我们之所以不断更新右边界,就是想知道在哪个位置之前必须得射出一只箭,这是必须依赖于右边界的值的。那我们是不是可以根据右边界从小到大进行排序,以每一个当前区间的右边界作为指标,判断其余区间是否能和它相交?

这样其实每一个右边界就是一次极限,必须射出一只箭的极限,因为如果到了右边界还不射出,那么当前这个气球就无法被击破了。所以根据以上思路,我们可以对右边界从小到大排序,得出以下:

代码思路:

假设给定的区间集合中的每个区间表示为一个点对[xstart, xend]

  1. 按照xend排序:首先将所有区间按照xend的值从小到大排序,这样可以确保我们每次选择的区间都是最先结束的。

  2. 选择区间:从排序后的区间列表中选择第一个区间,这个区间的xend将覆盖第一个坐标点。这将是我们选择的第一个区间。

  3. 覆盖剩余的点:然后遍历剩下的点,对于每个点,如果它没有被当前已经选择的区间集合中的任何一个区间覆盖,那么我们就选择下一个区间。我们选择的区间是排序后列表中第一个xstart小于或等于该点坐标的区间。

  4. 重复:重复上述过程,直到所有的点都被覆盖。

  5. 计数:在上述过程中,我们每选择一个区间就将计数器加一,最后的计数器的值就是最少需要的区间数。

3. 代码实现

3.1 思路一

image-20240301103352922

image-20240301103400613

3.2 思路二

image-20240301120054936

image-20240301120103742

3.3 思路三

image-20240301121019506

image-20240301121004056

4. 相关复杂度分析

解法一

  • 时间复杂度:O(n^2),最坏情况下,每个区间都会与每个箭进行比较。

  • 空间复杂度:O(1),没有使用额外的空间,除了几个变量以外。

解法二

  • 时间复杂度:O(n log n),排序的时间复杂度是O(n log n),遍历区间的时间复杂度是O(n),因此总的时间复杂度是O(n log n)。

  • 空间复杂度:O(1),排序可能需要O(log n)的空间复杂度(如果使用的是快速排序),但通常我们认为这是排序操作的内在空间使用,并且不会超过O(log n)。

解法三

  • 时间复杂度:O(n log n),与解法二类似,主要的时间消耗在于排序。

  • 空间复杂度:O(1),同样的理由,主要空间消耗在排序的栈空间上,不会超过O(log n)。

需要注意的是,实际的时间复杂度还取决于使用的排序算法。

在Java中,Arrays.sort()方法对于基本类型使用的是快速排序的变种,对于对象类型使用的是稳定的归并排序,归并排序的空间复杂度是O(n)。由于本题中排序的是int[][]类型,即对象类型,所以如果严格来说,空间复杂度应为O(n)。但在面试或者实际工作中,如果不是特别强调,我们通常认为这是内置的排序函数的空间复杂度,而不将其计入算法的空间复杂度中。

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

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

相关文章

nginx反向代理之缓存 客户端IP透传 负载均衡

一 缓存功能 缓存功能可以加速访问&#xff0c;如果没有缓存关闭后端服务器后&#xff0c;图片将无法访问&#xff0c;缓存功能默认关闭&#xff0c;需要开启。 相关选项&#xff1a; ​ proxy_cache zone_name | off; 默认off #指明调用的缓存&#xff0c;或关闭缓存机制;C…

有道翻译相关介绍

官网&#xff1a;有道翻译 (youdao.com) 翻文本与文档 长短句实时翻译&#xff0c;109种语言互译 支持医学、计算机、金融经济等专业领域翻译 提供42个专业术语库&上传自定义术语库 翻词汇 实时收录最新词汇&#xff0c;8种语言互译 完整收录新牛津、柯林斯、韦氏等权威…

redis7.2.2|Dict

文章目录 StructredisDBdictdictTypedictEntry 宏定义散列函数散列冲突dictEntry pointer bit tricks[指针位技巧]API implementation_dictReset_dictInitdictCreatedictGetHashdictSetKeydictSetValdictSetNextdictGetNextdictGetValdictGetKey_dictCleardictEmptydictRelease…

随机事件与概率

《1800》 独立 条件概率的运算思路 相互独立,则其各种运算皆独立 概率计算按照里面的因素的计算展开即可 概率值可以直接用分数表示

GaussDB跨云容灾:实现跨地域的数据库高可用能力

背景 金融、银行业等对数据的安全有着较高的要求&#xff0c;同城容灾建设方案&#xff0c;在绝大多数场景下可以保证业务数据的安全性&#xff0c;但是在极端情况下&#xff0c;如遇不可抗力因素等&#xff0c;要保证数据的安全性&#xff0c;就需要采取跨地域的容灾方案。 …

使用Python对数据进行rsa加密

#!/usr/bin/python3 import base64 import json import jsonpath import requests from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5 from base64 import b64decode, b64encodedef get_public_key():"""备注&#…

【java 基础】闲话 ClassLoader 和 SPI (一)

文章目录 引子双亲委派模型你真的明白了吗&#xff1f; 双亲委派“不够用了”SPI机制 其他琐碎 引子 有别于 java 提供的 IO 模块&#xff0c;java 中的classloader主要是用来加载类的&#xff0c;当然除了加载类&#xff0c;也可以加载资源文件。 那么首先我们会问一个问题&…

【内部消息】24上半年软考可能支持平板、PC和手机等多平台报名

根据内部消息&#xff0c;软考网上报名系统正在改革&#xff0c;之前只能通过PC端报名的&#xff0c;下次报名可能支持平板、手机等多终端进行网上报名了。现在官方并没有确切消息发出&#xff0c;这次变动可能发生在2024上半年&#xff0c;也有可能得到下半年才能实行。以下是…

优质线路、智能加速,向日葵助你轻松进行海外远控

如今&#xff0c;海外远控的需求越来越普遍。无论是企业&#xff0c;还是个人工作室&#xff0c;只要我们的工作生活需要接触到一些“海外元素”&#xff0c;需要进行跨境作业&#xff0c;那么远程控制都可以帮助到我们。 但传统的远程控制在进行海外远控时&#xff0c;往往会…

iconv 更改字符串编码操作

概要 在日常开发中&#xff0c;中文字符乱码是一个经常遇到的问题。在解决此问题时&#xff0c;遇到一个比较好用的字符串编码开源库&#xff0c;在此进行总结。 整体思路流程 iconv官网地址&#xff1a;http://www.gnu.org/software/libiconv/ 这里主要使用的相关接口&…

查看网络连接的netstat

netstat是一个监控TCP/IP网络的非常有用的工具&#xff0c;可以显示路由表、实际的网络连接&#xff0c;以及每一个网络接口设备的状态信息&#xff0c;可以让用户得知目前都有哪些网络连接正在运作。netstat用户显示与IP、TCP、UDP和ICMP协议相关的统计数据&#xff0c;一般用…

全链路仿真压测系统

1.项目背景 目前常用的压测工具一般都是针对QPS这一个单一指标进行考量。即使支持编写脚本的工具也只是通过参数化模拟用户。但是实际用户是使用单独设备请求服务器&#xff0c;即一个用户就是一个tcp连接。 所以为了更真实的模拟用户行为&#xff0c;我们需要通过一个tcp连接…