leetcode34.排序数组中查找元素第一个和最后一个位置两种解题方法(超详细)

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/?envType=list&envId=ZCa7r67M这道题,读者可能会说这道题有什么好讲的?

不就是双指针一次就出结果吗?

本题我们将以双指针和二分查找两种思路讲解,并且着重讲解二分查找的方法,和其中的代码实现细节,喜欢看代码细节的读者可以耐心看一看,相信会有一定的收获。

第一种解法是双指针

思路:由于数组有序,呈递增状态,我们要找的是给定目标值target,在该数组内部 出现的起始位置和终止位置,可以使用双指针从两边开始逐步探测,各指针都是只要找到target了,就停止寻找,直到两个位置都找到了target,就跳出,不用向里再寻找,此时的两指针指向位置正是答案,如果两指针指向一起也是正确的,因为可能target只出现了一次,如果循环结束还没找到结果(left<=right),那说明无法找到答案,返回{-1,-1}。

下面是可以通过的代码

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int l=0,r=nums.size()-1;while(l<=r){if(nums[l]==target&&nums[r]==target)return {l,r};if(nums[l]!=target)++l;if(nums[r]!=target)--r;}return {-1,-1};}
};

简洁的代码!你以为这就完了吗?判断的部分,也就是判断这两个位置对应元素是否都等于target这条代码一定要先写, 不能写在先更改l或者r的后面,看起来逻辑没有什么不同,怎么写都对,但是实际上是有代码错误的,比如这条测试用例nums:[1]    target: 0

虽然我们的判断逻辑是没问题的,但是先进行left和right的偏移,可能会使接下来的判断这两个位置对应元素是否都等于target这条代码出现越界错误。这是不容易被察觉到的错误,要小心。如果是先判断就不会有这样的错误,因为调整完left和right后,会进行的首先是越界判断,直接跳出循环了。

第二种解法二分查找

这一思路我用分别计算出左右边界的方法来讲解,这样写不容易出错,且思路清晰,代码有bug也更容易排查,强烈建议算法使用不是很熟练的读者使用这种写法。

解题思路就是分别求出左右边界,我讲其中之一,左边界,右边界与左边界思路是相同的,只是方向不同而已,稍加改动即可。

先给出代码根据代码进行分析

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int left=leftget(nums,target);int right=rightget(nums,target);if(left==-10||right==-10)return {-1,-1};if(right-left>1)return {left+1,right-1};return {-1,-1};}int leftget(vector<int>&nums,int target){int left=0,right=nums.size()-1;int res=-10;while(left<=right){int mid=(left+right)>>1;if(nums[mid]>=target){right=mid-1;res=right;}else left=mid+1;}return res;}int rightget(vector<int>&nums,int target){int left=0,right=nums.size()-1;int res=-10;while(left<=right){int mid=(left+right)>>1;if(nums[mid]<=target){left=mid+1;res=left;}else right=mid-1;}return res;}
};

细节非常多(只讨论left<=right)
把寻找左右界限分成了两个函数,然后求解
先看寻找边界的代码:
是用二分查找的方法写的,定义一个变量然后更新这个变量为返回值返回左右边界
寻找左边界,如果mid对应数据大于等于target将right左移,right=mid-1,大于target左移right天经地义,我们来看看等于时候
如果mid对应数据等于mid意味着什么?意味着我们要找的target范围数组就在此时的left——right之间,这个时候还左移right会使right最终移过target这片区域的最左端
正是这样,我们每一次进来,都更新左边界值为right。

几个疑问:
怎么确定right不断左移而最终会导致right走向target区域的左端点的?
其实整个过程不是通过一直的做right左移操作而使right一下子就确定了target区域的左端点的,而总是需要不停移left和right最终使right走向左端点
当mid对应数据大于等于target时候需要左移动right缩小范围,这时候target一定有全部或部分区域处在left——right这个范围内。
mid对应数据小于target也不一定就意味着target就一定不在范围内,可能是right移动完之后使得mid对应数据暂时小于target,这时候右移动left之后,可能会使mid对应数据重新大于等于target
如果是target不在left——right这种情境下,mid所取值一定是小于target了,所以left不停右移直到超出循环,而不会再更新最终target左窗口,也就是此时right的值。
上面的推论是保证right找到了左端点前一个位置时候,不会再乱动!

换句话说:当mid对应数据在target区间内时候,right每次更新为mid-1,也就是说只有两种可能right还是target,更新完right之后mid对应数据可能暂时性的小于targrt,但不要担心
我们调整left移动会最终使没走完的right继续左移动(这是mid由于right的缘故重新大于等于target)
如果上次mid指向的是起始点的target呢?那么right就指向了target区域的最左端点的左一个位置!!!这个时候mid一直会保持小于target
所以会一直右移动right,直到循环出去,不会更新数据,这也就是right为什么会保证一定处于target左端点前一个位置的重要原因!!
特例辩证:当right离边界很近的时候,left会不停缩减,直到仅剩一个元素,这个时候left和right所指那个元素一定是target,然后mid就是target,然后right向左移动
为什么一定指向right,假如left——right某时候只剩两个元素【0,1】而我们要找的是1,那么nums【0】==0会使left继续缩小范围
如果剩余的数据是【0,1,2】那也是一样的,right此时在0位置停下(说right离target边界近这个作为特例原因在于如果target离right远,比如在范围中间,那么肯定会正常的运行,如果很近那你可能不好想
为什么不会是right踩到边界然后退出循环,而是一定正好走到边界左一个位置?其实你举个例子就明白了)


为什么不先进行更新左边界再更新right?
因为没更新right之前,right此时的位置只能说明left——right中间的某位置确实是target所在的区域,而更新了right为mid-1,right此时就有可能作为target区域的左边界的左一个位置了
如果在不更新right时候去先更新最终答案,只会得到一个错误的答案,因为在最后一次左移动right数据时候,那个时候right可能处于target附近,那么这个时候就正好离我们要找的数据很近,
你可以调整返回值+1或者-1操作来通过一些用例,但是这并不能证明思路是正确的,这很可能是凑巧,并没什么逻辑性可言。
也很可能right离target很远,mid才是指向最后一个左端点位置,这个你找到的right一定是错误答案,这也就说明了如果你先更新最终要返回的边界数据,然后依赖于对这个数据+-1,调整
这是错误的想法,一开始我就对这个代码顺序进行了调整,因为我发现左右边界返回值始终都需要对左边界+1,右边界-1,所以萌生出先更新边界再更新right可能会得到简单的不需要后续处理的正确答案,
但实际上这种想法是十分愚蠢的。

左端点求解和右端点的求解很类似,它们是相同的思路,只不过方向不同而已,上面我们讲了左端点,就不再赘述右端点,类比一下就可以了
再来看有没有其他的问题,值得我们讲解。确实有一个而且是隐藏很深的问题。
在讲这个问题之前,先来看一些简单的,比如最后的处理数据方面,最后的处理数据返回答案方面,有三种情况需要讨论。
第一种是如果返回回来的值也就是左右边界其中之一为初始化时候的垃圾值,那么直接返回{-1,-1}这种情况是在说明target目标值范围处在整个给定数组的左边或者右边,简单地说就是出界了,
给定数组里不仅没有,而且也不可能有比如说给定数组是【1,2,3】而target为10,这种情况就是。
这种超边界的情况下,自己模拟一下代码就知道,求左边界时候,更新左边界的代码根本进不去,所以也就更新不了。它引出了我们后面要说的,为什么不把求解左右边界函数里的左右边界的初始化值
初始化为-1,这样不是更直观吗?这个我们放在后面讲解。

第二种情况是right-left>1才能进入真正的返回,也就是找到了答案子数组,有人会说这不对啊,为什么不是大于等于?这不会落下只有单独一个target的情况吗?
一开始我也是这么想,后来改代码测试一下,发现答案是错误的,
拿两种测试用例来分析
【1】【5,7,7,8,8,10】这两种,第一个target=1,第二个target=6,你会发现反倒是target=1这种看起来不可能过的用例通过了,但是其他的某些用例过不去。
这和返回值有关,你会发现它是先比较的左右边界返回值,而我们知道它的左右边界是要做处理的,比如【1】它的左右边界会返回【-1,1】而做了处理才会是真答案也就是【0,0】
所以不用担心这种target只有一个的,它也会正确输出,但是【5,7,7,8,8,10】target=6这种不一样,它代表了我们需要过滤出去的第二个错误情况,也就是给定数组的范围包含target,但实际上target不存在于
该数组中,这种情况会使得最终的left和right会缩在一起,因为它无法找到答案,但是target却确确实实的处在给定数组的范围内,以这个用例而言左边界的left和right会缩到1下标处也就对应数据7
然后mid过大,right移动向左,处在0这个位置上,跳出循环。而右边界是移动left然后取答案的,模拟可以知道,left和right都会处在7这个数据,然后right更新向左,右边界函数会返回此时left所在区域也就是1
这个下标,这样右边界减去左边界正好等于1,没错正好把错误的情况取答案了!所以不能写成大于等于1,而是大于1。
你可以这样想,如果左右边界取完差值等于1是什么情况(没做处理之前)?就是left和right在左右边界取时候,分别在没有找到target时候,交叉的越界,且距离为1。
这是典型的,target处在给定数组数据范围内,而不实际出现于给定数组中,请大家格外注意这种情况。
这种求解左右边界的代码如果遇到target只有1个的情况时候,会拉开距离返回去,也就是说,一定会在target下标的左右偏移1的位置出现,也就是说一定大于1,只有target有0个时候,才会拉不开距离相减等于1

第三种情况是直接返回{-1,-1}这种情况对应的其实就是情况二里找不到时候,应该返回的【-1,-1】因为上面我们分析的是为什么不写>=1,这个等于1就是错误答案,在后面返回-1就可以了
取到正确答案时候,在情况二中返回正确序列即可。

最后我们终于可以说到这个隐晦错误了,为什么左右边界函数边界返回值不能初始化为-1?
这个是由测试用例【1】这种类型引起的错误,当target只有1个,而且target在左边界时候,就会出现左边界取到-1,这个时候如果你的边界初始化就是-1,且以该值作为错误答案判断依据时候
就会发生错误,原本正确的解被丢弃。所以我们初始化应该取一个除了小于0且不等于-1的数,从这些里初始化值就正确了,

至此把所有疑点全部讲解完毕。

还有就是最后的判断部分读者可能感觉有点奇怪,考虑对返回答案做一些适当的调整,不过不要忘记对可能出错误的判断部分做出调整,主要是:对于target不在数组内部和target在数组范围但是不在数组里
这两种情况,做出调整后,题解依然正确。

给出一个更改后的示例代码:

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int left=leftget(nums,target);int right=rightget(nums,target);if(left==-9||right==-11)return {-1,-1};if(right-left>=0)return {left,right};return {-1,-1};}int leftget(vector<int>&nums,int target){int left=0,right=nums.size()-1;int res=-10;while(left<=right){int mid=(left+right)>>1;if(nums[mid]>=target){right=mid-1;res=right;}else left=mid+1;}return res+1;}int rightget(vector<int>&nums,int target){int left=0,right=nums.size()-1;int res=-10;while(left<=right){int mid=(left+right)>>1;if(nums[mid]<=target){left=mid+1;res=left;}else right=mid-1;}return res-1;}
};

本期内容就到这里

如果对您有用的话别忘了一键三连哦,如果是互粉回访我也会做的!

大家有什么想看的题解,或者想看的算法专栏、数据结构专栏,可以去看看往期的文章,有想看的新题目或者专栏也可以评论区写出来,讨论一番,本账号将持续更新。
期待您的关注

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

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

相关文章

云计算(Docker)

Docker简介 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言&#xff0c;并遵从 Apache2.0 协议开源。它可以让开发者打包应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。Docker 可用于开发…

详解ssh远程登录服务

华子目录 简介概念功能 分类文字接口图形接口 文字接口ssh连接服务器浅浅介绍一下加密技术凯撒加密加密分类对称加密非对称加密非对称加密方法&#xff08;也叫公钥加密&#xff09; ssh两大类认证方式&#xff1a;连接加密技术简介密钥解析 ssh工作过程版本协商阶段密钥和算法…

程序员如何做事更细致?

最近在工作中老是犯一些小错误&#xff0c;哦&#xff0c;当然也不是最近了&#xff0c;其实我一直是个马虎的人&#xff0c;我很讨厌做一些细活&#xff0c;因为这会让我反复改动多次在会成功&#xff0c;而平时的代码由于有debug&#xff0c;即便出错了&#xff0c;再改回来即…

基于STC12C5A60S2系列1T 8051单片的模数芯片ADC0809实现模数转换应用

基于STC12C5A60S2系列1T 8051单片的模数芯片ADC0809实现模数转换应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍模数芯片ADC0809介绍通过模数芯片ADC0809把电压模…

Java Swing商品信息查询系统

内容要求 1&#xff09; 本次程序设计是专门针对 Java 课程的,要求使用 Java 语言进行具有一定代码量的程序开发。程序的设计要结合一定的算法&#xff0c;在进行代码编写前要能够设计好自己的算法。 2&#xff09;本次程序设计涉及到 Java 的基本语法&#xff0c;即课堂上所…

redis高级案列case

案列一 双写一致性 案例二 双锁策略 package com.redis.redis01.service;import com.redis.redis01.bean.RedisBs; import com.redis.redis01.mapper.RedisBsMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; imp…

基于STC12C5A60S2系列1T 8051单片机的模数芯片ADC0832实现模数转换应用

基于STC12C5A60S2系列1T 8051单片的模数芯片ADC0832实现模数转换应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍模数芯片ADC0832介绍通过模数芯片ADC0832把电压模…

【python】OpenCV—Rectangle, Circle, Selective Search(1.2)

文章目录 1 画框画圈1.1 画矩形框1.2 画圆 / 点1.3 椭圆 2 Selective Search3 Resize 1 画框画圈 1.1 画矩形框 # Copy the image img_rgb_copy img_rgb.copy()# Draw a rectangle cv2.rectangle(img_rgb_copy, pt1 (405, 90), pt2 (740, 510),color (255, 0, 0), thickne…

4种经典的限流算法

0、基础知识 1000毫秒内&#xff0c;允许2个请求&#xff0c;其他请求全部拒绝。 不拒绝就可能往db打请求&#xff0c;把db干爆~ interval 1000 rate 2&#xff1b; 一、固定窗口限流 固定窗口限流算法&#xff08;Fixed Window Rate Limiting Algorithm&#xff09;是…

文件传输客户端 SecureFX mac中文版支持多种协议

SecureFX mac是一款功能强大的文件传输客户端&#xff0c;可在 Mac 操作系统上使用。它由 VanDyke Software 公司开发&#xff0c;旨在为用户提供安全、可靠、高效的文件传输服务。 SecureFX 支持多种协议&#xff0c;包括 SFTP、SCP、FTP、FTP over SSL/TLS 和 HTTP/S。它使用…

支持4KHz回报还能无线充电,简约不简单的雷柏VT3S游戏鼠标上手

这两年国产鼠标的表现很让人惊喜&#xff0c;不仅外观做工越来越精细&#xff0c;配置也越来越强大&#xff0c;当然价格依然亲民。现在很容易找到一款搭载高端传感器、响应速度快、电池续航时间长&#xff0c;并且还支持无线充电的全能型鼠标。 我之前用雷柏的鼠标比较多&…

Transformer ZOO

Natural Language Processing Transformer:Attention is all you need URL(46589)2017.6 提出Attention机制可以替代卷积框架。引入Position Encoding&#xff0c;用来为序列添加前后文关系。注意力机制中包含了全局信息自注意力机制在建模序列数据中的长期依赖关系方面表现出…

vue项目本地开发完成后部署到服务器后报404

vue项目本地开发完成后部署到服务器后报404是什么原因呢&#xff1f; 一、如何部署 前后端分离开发模式下&#xff0c;前后端是独立布署的&#xff0c;前端只需要将最后的构建物上传至目标服务器的web容器指定的静态目录下即可 我们知道vue项目在构建后&#xff0c;是生成一系…

统信UOS通过源码安装软件提示“configure: error: cannot run C compiled programs.”错误

1. 问题说明 使用源码的方式安装git软件&#xff0c;安装过程中出现两个错误。 编译错误“cannot run C compiled programs” XC:~/Downloads/git-2.42.1$ ./configure --prefix/home/software/git-2.42.1 configure: Setting lib to lib (the default) configure: Will try…

计算机组成原理-双端口RAM和多模块存储器

文章目录 存取周期总览双端口RAM多体并行存储器低地址交叉编址有多少个存储体合适&#xff08;体号&#xff09;多模块存储器&#xff08;多体存储器&#xff09;总结实际场景 存取周期 总览 双端口RAM RAM&#xff1a;用于主存或高速缓存&#xff0c;断电数据丢失 多体并行…

C++ 运算符重载详解

本篇内容来源于对c课堂上学习内容的记录 通过定义函数实现任意数据类型的运算 假设我们定义了一个复数类&#xff0c;想要实现两个复数的相加肯定不能直接使用“”运算符&#xff0c;我们可以通过自定义一个函数来实现这个功能&#xff1a; #include <iostream> using…

宠物信息服务预约小程序的效果如何

宠物的作用越来越重要&#xff0c;因此铲屎官们对自己爱宠的照顾也是加倍提升&#xff0c;而市场围绕宠物展开的细分服务近些年来逐渐增多&#xff0c;且市场规模快速增长。涉及之广&#xff0c;涵盖宠物衣食住行、医疗、美容、婚丧嫁娶等&#xff0c;各品牌争相抢夺客户及抢占…

代码随想录算法训练营|五十六天

回文子串 647. 回文子串 - 力扣&#xff08;LeetCode&#xff09; dp含义&#xff1a;表示区间内[i,j]是否有回文子串&#xff0c;有true&#xff0c;没有false。 递推公式&#xff1a;当s[i]和s[j]不相等&#xff0c;false&#xff1b;相等时&#xff0c;情况一&#xff0c;…

中国电影票房排行数据爬取及分析可视化

大家好&#xff0c;我是带我去滑雪&#xff01; 对中国电影票房排行数据的爬取和分析可视化具有多方面的用处&#xff1a;例如了解电影市场的历史趋势&#xff0c;包括不同类型电影的受欢迎程度、票房的季节性波动。识别观众对于不同类型电影的偏好&#xff0c;为电影制片方提供…

高效背单词——单词APP安利

大英赛&#xff0c;CET四六级&#xff0c;以及考研英语&#xff0c;都在不远的未来再度来临&#xff0c;年复一年的考试不曾停息&#xff0c;想要取得好成绩&#xff0c;需要我们的重视并赋予相应的努力。对于应试英语&#xff0c;词汇量是不可忽略的硬性要求。相比于传统默写&…