【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置

本节博客主要是通过“在排序数组中查找元素的第一个和最后一个位置”总结关于二分算法的左右界代码模板,有需要借鉴即可。

目录

  • 1.题目
  • 2.二分边界算法
    • 2.1查找区间左端点
      • 2.1.1循环条件
      • 2.1.2求中点的操作
      • 2.1.3总结
    • 2.2查找区间右端点
      • 2.1.1循环条件
      • 2.1.2求中点的操作
      • 2.1.3总结
    • 2.3总结
  • 3.参考解题代码
  • 4.模板总结
  • 5.总结

1.题目

题目链接:LINK
在这里插入图片描述

这个题要求我们求这个排序数组的一个元素的开始位置与结束位置。

可以用暴力求解的方法,把第一次出现的数字下标记录一下,最后一次记录一下,返回结果,除了复杂度差之外没什么不好的。

当然我们这里说一下二分算法的思想。之所以可以使用二分算法,这是因为该数组是有序的,可以利用二分算法的“二段性”将其分割。


用两次二分算法:

  • 一方面,我们可以将整个数组分为大于等于t和小于t来找left点

  • 另一方面,我们可以将整个数组分为大于t和小于等于t来找right点

但是这里有一些代码细节值得注意!!!

2.二分边界算法

2.1查找区间左端点

在这里插入图片描述

思考:我们在寻找左端点时候为什么要对数组按照小于t和大于等于t进行划分?
答:关键是因为我们要找左端点,左端点一定不可能在小于t的区间里。
在这里插入图片描述

通过上面的图片可知,我们要想找到一个数的左端点,那么这个左端点(我们要寻找的点)一定不再大于t这个区域,所以我们可知

  • mid < ret时,left = mid + 1
  • mid >= ret时,right = mid

2.1.1循环条件

while(left < right)//... √
while(left <= right)//... ×

循环条件选:left < right

这里为什么不是left <= right 呢?

  • left==right的情况下,即是最后结果,无需进行重复判断。
  • 可能有些情况下会出现死循环问题
    下面是对上面两个理由进行论证:
    在所有可能情况中,无非存在三种情况,
  • ①left与right中间存在要找的ret点
    在这里插入图片描述
    此时,mid = ret,mid == right,那么left = mid,会不断进入循环,陷入死循环
  • ②left与right中间所有点全部大于我们要找的右端点
    在这里插入图片描述
    到了最后,mid > ret, mid = right,right = mid,会存在死循环问题
  • ③left与right中间所有点全部小于我们要找的右端点
    在这里插入图片描述
    mid < ret,left = mid + 1,不会出现死循环问题。

2.1.2求中点的操作

我们求中点无非两种求法

①mid = left + (right - left) / 2; √
②mid = left + (right - left + 1) / 2; ×

这俩主要区别就是在数字个数是偶数情况下,①式取靠左的中点;②式取靠右的中点。

然后对于查找区间右端点而言,必须选用①式。
为什么,下面来进行解释?
如果选用②式,会存在下面情况:比如,mid指向right,然后mid所在的值>=ret值,就会不断死循环
注:if mid >= ret,right = mid;

在这里插入图片描述

2.1.3总结

在求目标值左端点时候,第一循环条件不能有等于,第二是求中点要用靠右中点。

2.2查找区间右端点

在这里插入图片描述

通过上面的图片可知,我们要想找到一个数的左端点,那么这个右端点(我们要寻找的点)一定不再大于t这个区域,所以我们可知

  • mid <= ret时,left = mid
  • mid > ret时,right = mid - 1

2.1.1循环条件

while(left < right)//... ×
while(left <= right)//... √

循环条件选:left < right

这里为什么不是left <= right 呢?

  • left==right的情况下,即是最后结果,无需进行重复判断。
  • 可能有些情况下会出现死循环问题

下面是对上面两个理由进行论证:
在所有可能情况中,无非存在三种情况,

  • ①left与right中间存在要找的ret点
    在这里插入图片描述
    此时,mid = ret,mid == right,那么right = mid,会不断进入循环,陷入死循环。
  • ②left与right中间所有点全部大于我们要找的右端点
    在这里插入图片描述
    到了最后,mid > ret,mid == right,right = mid - 1,不会出现死循环问题
  • ③left与right中间所有点全部小于我们要找的右端点
    在这里插入图片描述
    mid < ret,left = mid,此时会出现死循环问题

2.1.2求中点的操作

我们求中点无非两种求法

①mid = left + (right - left) / 2; ×
②mid = left + (right - left + 1) / 2;

这俩主要区别就是在数字个数是偶数情况下,①式取靠左的中点;②式取靠右的中点。

然后对于查找区间右端点而言,必须选用②式。
为什么,下面来进行解释?
如果选用①式,会存在下面情况:比如,mid指向left,然后mid所在的值<=ret值,left = mid,如此就会不断死循环
注:if mid <= ret,left = mid;
在这里插入图片描述

2.1.3总结

在求目标值右端点时候,第一循环条件不能有等于,第二是求中点要用靠右中点。

2.3总结

找左端点:
在这里插入图片描述

  • mid < ret时,left = mid + 1
  • mid >= ret时,right = mid
while(left < right)//...
mid = left + (right - left) / 2;

找右端点:
在这里插入图片描述

  • mid <= ret时,left = mid
  • mid > ret时,right = mid - 1
while(left < right)//...
mid = left + (right - left + 1) / 2;

根据上面的算法总结我们可以解决上面题目

3.参考解题代码

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {vector<int> ret;//处理特殊情况if(nums.size() == 0){ret.push_back(-1);ret.push_back(-1);return ret;}int left = 0, right = nums.size() - 1;//处理左端点while(left < right){int mid = left + (right - left) / 2;if(nums[mid] >= target){right = mid;}else{left = mid + 1;}}if(nums[left] == nums[right] && nums[left] == target){ret.push_back(left);}else{ret.push_back(-1);}//处理右端点left = 0, right = nums.size() - 1;while(left < right){int mid = left + (right - left + 1) / 2;if(nums[mid] > target){right = mid - 1;}else{left = mid;}}if(nums[left] == nums[right] && nums[right] == target){ret.push_back(right);}else{ret.push_back(-1);}return ret;}
};

4.模板总结

在这里插入图片描述

5.总结

这个题目我感觉掌握了二分边界代码原理其实不难,重点肯定是那个二分边界算法原理,需要自己多理解一下。


EOF

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

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

相关文章

JavaScript-基本数据类型和变量

基本数据类型 JavaScript支持数字、字符串和布尔值3种基本数据类型 字符串型 字符串型是JavaScript用来表示文本的数据类型&#xff0c;字符串通常由单引号或双引号括起来&#xff0c;如果字符串存在特殊字符&#xff0c;可以用转义字符代替 数字型 数字型也是JavaScript中的基…

每日一题——力扣206. 反转链表(举一反三、思想解读)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三题目链接 目录 菜鸡写法​编辑 代码点评 代码分析 时间复杂度 空间复杂度 专业点评 另一种方法​编辑 代码点评 代码逻辑 时间复杂度 空间…

Django Celery 的配置及使用---最详细教程

Django Celery 的配置及使用 Redis提供队列消息功能 一、安装redis 系统版本&#xff1a;Ubuntu 20.041、获取最新软件包 sudo apt update sudo apt install redis-server2、安装完成后&#xff0c;Redis服务器会自动启动。查看redis是否启动成功 sudo systemctl status …

iOS ------ 多线程基础

一&#xff0c;进程和线程 1&#xff0c;进程 定义&#xff1a; 进程是指在系统中正在运行的一个应用程序每个进程之间是独立的&#xff0c;每个进程均运行在其专有的且受保护的内存进程是系统进行资源分配和调度的一个独立单位 补充&#xff1a;iOS系统是相对封闭的系统&a…

工厂自动化升级改造(3)-Modbus与MQTT的转换

什么是MQTT,Modbus,见下面文章 工厂自动化升级改造参考(01)--设备通信协议详解及选型-CSDN博客文章浏览阅读608次,点赞9次,收藏6次。>>特点:基于标准的以太网技术,使用TCP/IP协议栈,支持高速数据传输和局域网内的设备通信。>>>特点:跨平台的通信协议,…

ArrayList和LinkedList的使用

ArrayList List<> list new ArrayList<>(); LinkedList

XML文件转TXT文件 yolo标签转换(代码可直接使用) 可批量转换

像这样的xml文件&#xff0c;我们可以通过代码批量转换为txt文件格式&#xff1a; 新建一个xml2txt.py文件&#xff0c; 上代码&#xff0c;直接复制粘贴 import xml.etree.ElementTree as ET import osdef convert(size, box):x_center (box[0] box[1]) / 2.0y_center (box…

怎么把照片变小做头像?多种方法教你图片改尺寸

现在在社交媒体平台或者是社交软件上&#xff0c;我们经常会去更改头像来展示自己&#xff0c;但是有时候我们拍摄的照片太大无法直接用作头像&#xff0c;这时候就需要去修改图片尺寸&#xff0c;将图片改大小到合适的数值才能使用&#xff0c;那么如何快速的将图片改大小呢&a…

使用Python处理Excel数据:去除列中的双引号

目录 引言 技术背景 步骤概述 代码示例 案例分析 扩展内容 1. 处理多个列中的双引号 2. 处理大型Excel文件 3. 自定义函数处理数据 4. 错误处理和日志记录 结论 引言 在当今信息爆炸的时代&#xff0c;数据已经成为了各个行业最宝贵的资源之一。而Excel&#xff0c…

Python自学之路--004:Python使用注意点(原始字符串‘r’\字符转换\‘wb’与‘w区别’\‘\‘与‘\\’区别)

目录 1、原始字符串‘r’ 2、字符转换问题 3、open与write函数’wb’与’w’区分 4、Python里面\与\\的区别 1、原始字符串‘r’ 以前的脚本通过Python2.7写的&#xff0c;通过Python3.12去编译发现不通用了&#xff0c;其实也是从一个初学者的角度去看待这些问题。 其中的\…

C# 结合 JavaScript 对 Web 控件进行数据输入验证

目录 关于数据验证 范例运行环境 验证设计 JavaScript 方法 设计 实现 调用示例 C# 方法 设计 实现 调用示例 小结 关于数据验证 在 Web 应用的录入界面&#xff0c;数据验证是一项重要的实现功能&#xff0c;数据验证是指确认 Web 控件输入或选择的数据&#xff…

Minio 对象存储 OSS概述

系列文章目录 第五章 Minio 对象存储 OSS概述 Minio 对象存储 OSS概述 系列文章目录对象存储 OSS基本概念存储空间&#xff08;Bucket&#xff09;对象&#xff08;Object&#xff09;ObjectKeyRegion&#xff08;地域&#xff09;Endpoint&#xff08;访问域名&#xff09;Ac…