【优选算法 二分查找】二分查找入门详解:二分查找 & 在排序数组中查找元素的第一个和最后一个位置



二分查找
题目描述

题目解析

暴力解法
我们可以从左往右遍历一次数组,如果存在 target 则返回数组的下标,否则返回 -1;
时间复杂度 O(N),因为没有利用数组有序的特点,每次比较只能舍弃一个要比较的数;
二分查找法
所以我们可以把 [1 , 4 ] 这个区间舍弃掉,直接看 [ 5 , 8 ];仅仅拿 4 和 5 比较一次,就干掉了一大片数;

总结
在一个数组中,随便找一个数,拿这个数和 target 进行比较,并且以拿的这个数分成两个区间,比较后舍弃一部分数,然后再从另一个区间的数中,找下一个要和 target 比较的数;
二分查找算法的本质,就是当数组具有“二段性”时,哪怕这个数组是无序的,只要能在这个数组中发现“二段性”,那么也可以使用二分查找;
“二段性”
当我们发现一个规律,然后根据这个规律,选取某一个点,根据这个点,能把数组分成两个区域,根据规律能够有选择地舍弃一部分,进而在另一个部分继续查找的时候,此时,就可以使用二分查找的算法

我们要从数组中找和 target 进行比较的数,不一定是先从 n/2 的位置开始找,只要找的这个数的点,能把这个区间分成两部分,即满足二段性,进而使用二分查找法;
但是哪怕那么多点可以选择,但是我们都应该先选择 n/2 的位置的点,因为在概率学的数学期望中,我们选择中间的点,时间复杂度是最好的;

- nums[mid] < target,left = mid+1
- nums[mid] = target,return mid
- nums[mid] > target,right = mid-1

细节问题
因为当 left = right 的时候,也是要继续判断的,所以 left <= right 为循环判断条件;
时间复杂度,只需要看循环执行多少次:

如何求 x 呢?

int mid = (left + right) / 2,mid 会有溢出风险:

如果想不从 n/2 开始查找,可以修改,如下面的写法是从 n/3 开始查找的:

在排序数组中查找元素的第一个和最后一个位置


题目解析

发现二段性
方法一:利用数组有序特性,使用简单的二分查找法
如果使用简单的二分查找,创建左指针和右指针,再找中间值,对这个中间值分别进行讨论;如果对于一些特点的情况,如下面的这种情况,这个方法的时间复杂度会非常高;

虽然 mid 指针刚好指向 target,但是不能确定此时的 mid 指向的是在目标连续区域的哪一个位置,从而不好找目标区域的起始位置和终点位置;
所以使用简单二分查找的方法,对于上述情况和类似情况,查找的时间复杂度会近似于暴力查找;
方法二:在简单二分查找方法的基础上,对二分策略进行优化
回到题目的规律:当发现题目具有二段性,就可以利用二分查找;

因为不能同时查找目标区间起始位置和终点位置,所以我们可以先查找起始位置;

利用二段性,我们可以根据目标值起始位置,把数组区间分成两个部分;
左边区域的值都是小于 target,右边区域的值都大于等于target,根据 target,在查询目标区域左端点的时候,发现这个数组是有二段性,因此可以使用二分查找:

查找区间左端点
以 mid 的值来决定 left 和 right 的更新方法
我们设 mid 指向的值为 x,我们要找 >=target 区间的起始位置,拿 x 的值与 target 讨论;

- 1. x < target,此时 mid 落在 < target 的区域,不可能有最终结果,所以继续往下执行:
- 2. x >= target
(不同点,之前简单二分查找的方法分三种情况讨论,这里只分两种情况,因为 x= target 并不是最终的结果,因为最终的结果的左端点和右端点)
对于这种情况,对应的做法:
我们虽然[mid , right] 区间的情况一定是都大于 target,但是不知道 [left , mid] 区间的具体情况是都小于 target 还是部分小于 target;所以 right 绝对不能移动到 [left , mid] 这个不确定的区间,因为如果 mid 在修改之前就是目标区间的左端点,让 right = mid -1,[left , right]这个区间就没有 target 了,所以 right = mid 即可;
处理细节问题
1.循环条件
对于执行循环操作(二分查找操作)的第一个循环条件,是 left <= right,还是 left < right 呢?

一定是选择 left< right ,为什么呢?有两个原因;
- 原因一:因为 left = right 的情况不需要放到循环中判断
我们分三种情况来讨论(ret 为返回的最终结果):
- 1. 数组中有元素等于 target
前面提到,left 刚开始一直处于 < target 的区间,在 x>= target 的条件下,right=mid,所以right 一定在 ret 右边的区间,threshold 为ret (第一个 target 元素)的前一个元素;
因为 left 和 right 的调整方式是 left = mid+1,right = mid,所以 right 一直在>= target 的区域移动,而 left = mid +1,说明 left 是一直想要跳出 < target 的区域的;
所以当 left 和 right 调整到最后,循环结束,此时 left 一定会和 right 相等,此时我们在循环结束后单独判断相遇点和 target 是否相等即可;如果 left = right ,那么两个指针指向的位置,一定就是目标区间左端点 ret,所以循环条件只需要判断 left < right 即可;
- 2. 数组中所有元素全都大于 target
如果所有数组元素都大于 target,那么整个过程只有 right 指针在不断向左边移动,直到 left 和 right 相遇;
哪怕相遇,也没有最终结果,所以这种情况下,我们只需要在left < right 时执行循环,循环退出,left=right;
此时我们只需要拿当前两个指针指向的值和 target 比较:相同则回到第一种情况,这个值就是目标区间的左端点;不相同则返回 [-1,-1];
- 3. 数组中所有元素全都小于 target
这种情况和第二种情况同理:
整个更新过程都只有 left 在移动,循环退出时,left = right,只需要判断当前指针的值和 target 是否相等即可,相等则这个值就是目标区域的起始位置 ret,否则返回 [-1,-1];
- 原因二:如果在循环条件中判断 left = right,会出现死循环
什么时候会出现死循环,就是整个数组中有元素等于 target 的时候:

如果循环条件是 while(left <= right){ ..... },此时left = right ,更新的 mid还是指向 left,right 所在位置,此时满足 x <= target 条件,right=mid,所以 right = left,left 和 right 就会一直停在一个地方循环更新结果,这种情况下就会出现死循环;
2. 求中点操作
求中点操作,也就是在定义 left 和 right 之后,求 mid 的操作;
- 1. mid = left + ( right - left ) / 2 ;
- 2. mid = left +( right - left +1 ) /2 ;
这两种求中点的操作,区别在于数组元素是偶数时,更新 mid 的位置不同;这两种方法在简单二分情况,如第一题的情况都是可以用的,但是这种情况下不能用 mid = left + (right - left +1)/2 求中点;
如果是用第二种方法 mid = left + (right - left +1)/2,此时 mid 的位置:
- 如果上图条件中,target > 3,那么此时 mid < target ,left = mid+1,程序结束,此时是没问题的;
- 但是,如果 target <=3,那么此时 mid>=target,此时调整 right = mid,就又进入死循环了:
所以,在求左端点时,只能用 mid = left +(right - left )/2 来求中点;

查找区间右端点
根据查找右端点,依旧可以把整个区间分成两部分,<=target,>target :

根据 mid 与 target 的关系决定更新操作

1. x <= target
因为我们要保证在调整 left 和 right 的过程中,右端点一定要在 [left,right] 区间,所以当前这种情况,我们应该移动 left:
2. x > target
因为 x > target,所以mid 所在位置一定是在目标区域之外,因此更新 right = mid -1;
处理细节问题
1. 循环条件

2. 求中点的操作
循环条件和求左端点相同,不同的是求中点的公式
- 1. mid = left + ( right - left ) / 2 ;
- 2. mid = left +( right - left +1 ) /2 ;
这两种求中点的操作,区别在于数组元素是偶数时,更新 mid 的位置不同;这两种方法在简单二分情况,如第一题的情况都是可以用的,但是这种情况下不能用 mid = left + (right - left )/2 求中点;
为了接下来的操作不和求左端点的时候弄混,要牢记此时要求的是右端点,接下来的假设都是为了能求出右端点:
如果是用第二种方法 mid = left + (right - left )/2,此时 mid 的位置:
- 如果上图条件中,target < 2,那么此时 mid > target ,right = mid-1,程序结束,此时是没问题的;
- 但是,如果 target >=3,那么此时 mid>=target,此时调整 left = mid,就又进入死循环了:
所以,在求右端点时,只能用 mid = left +(right - left +1 )/2 来求中点,在求右端点时,为什么用这条公式求中点不会出现死循环呢?
用 mid = left +(right - left +1 )/2 来求中点,此时 mid 在 right 的位置,如果此时 x > target,right=mid-1,此时不满足 left<right 的条件,会结束循环;如果 x<= target,left = mid :
此时也不满足 left<right 的条件,也会结束循环,所以在求右端点的时候,用 mid = left +(right - left +1)/2 来求中点,就不会出现死循环;
编写代码

总结二分模板



相关文章:
【优选算法 二分查找】二分查找入门详解:二分查找 & 在排序数组中查找元素的第一个和最后一个位置
二分查找 题目描述 题目解析 暴力解法 我们可以从左往右遍历一次数组,如果存在 target 则返回数组的下标,否则返回 -1; 时间复杂度 O(N),因为没有利用数组有序的特点,每次比较只能舍弃一个要比较的数&…...
WPF编写工业相机镜头选型程序
该程序满足面阵和线阵的要求。 前端代码 <Window x:Class"相机镜头选型.MainWindow" Loaded"Window_Loaded"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…...
网络安全内容整理二
网络嗅探技术 网络监听 网络监听,也称网络嗅探(Network Sniffing):在他方未察觉的情况下捕获其通信报文、通信内容的技术 网卡的工作模式: 1.广播模式(Broadcast Mode):网卡能够接收网络中的广播信息 2.组播模式(Multicast Mo…...
解决git did not exit cleanly (exit code 128)问题
解决 git did not exit cleanly (exit code 128)问题 1、错误描述2、解决方法2.1 方法一2.2 方法二 1、错误描述 使用TortoiseGit进行操作时,总是提示下述错误。 2、解决方法 2.1 方法一 打开 TortoiseGit -> Settings 点击 Network&…...
Linux入门攻坚——40、Linux集群系统入门-lvs(1)
Cluster,集群,为了解决某个特定问题将多台计算机组合起来形成的单个系统。 这个单个集群系统可以扩展,系统扩展的方式:scale up,向上扩展,更换更好的主机;scale out,向外扩展&…...
momentum 和 weight_decay 的区别
momentum 和 weight_decay 的区别 两者在优化器中的作用不同,主要体现在优化的目的和机制上。 1. momentum(动量) 作用:加速收敛并减少优化过程中的震荡。 机制: momentum 是用于在梯度下降中积累动量的机制。它通过在每一步中综合之前的更新方向,帮助模型在陡峭区域加速…...
Vue 3 + TypeScript进阶用法
在掌握了 Vue 3 和 TypeScript 的基本使用后,你可以进一步探索一些进阶特性和最佳实践。这些包括更复杂的类型定义、自定义 hooks、全局状态管理等。下面是一些关键点: 1. 更复杂的类型定义 Props 和 Emits 的类型 对于组件的 props 和 emits…...
AbutionGraph-时序向量图谱数据库-快速安装部署
运行环境 1)操作系统 最好是使用CentOS7或者Ubuntu18以上系统,不满足的话请升级系统内核gcc版本至8以上版本。 支持所有国产主流操作系统银河麒麟、统信OS、深度等等,均做过兼容性测试; 2)CPU 为确保数据库每个进…...
Delphi-HTTP通讯及JSON解析
HTTP POST 请求函数 HttpPost 此函数用于发送带有JSON内容的POST请求到指定的URL,并接收服务器响应。它包括了必要的异常处理,确保在遇到错误时可以记录日志。 参数: sUrl:目标URL。sJson:要发送的JSON格式字符串。 返…...
Postgres 如何使事务原子化?
原子性(“ACID”意义上的)要求 对于对数据库执行的一系列操作,要么一起提交,要么全部回滚;不允许中间状态。对于现实世界的混乱的代码来说,这是天赐之物。 这些更改将被恢复,而不是导致生产环境…...
[Vue3]简易版Vue
简易版Vue 实现ref功能 ref功能主要是收集依赖和触发依赖的过程。 export class Dep { // 创建一个类,使用时使用new Depconstructor(value) { // 初始化可传入一个值this._val value;this.effects new Set(); //收集依赖的容器,使用set数据结构}…...
ElasticSearch学习记录
服务器操作系统版本:Ubuntu 24.04 Java版本:21 Spring Boot版本:3.3.5 如果打算用GUI,虚拟机安装Ubuntu 24.04,见 虚拟机安装Ubuntu 24.04及其常用软件(2024.7)_ubuntu24.04-CSDN博客文章浏览阅读6.6k次࿰…...
LabVIEW算法执行时间评估与Windows硬件支持
在设计和实现复杂系统时,准确估算算法的执行时间是关键步骤,尤其在实时性要求较高的应用中。这一评估有助于确定是否需要依赖硬件加速来满足性能需求。首先需要对算法进行时间复杂度分析并进行实验测试,了解其在Windows系统中的运行表现。根据…...
经验帖 | Matlab安装成功后打不开的解决方法
最近在安装Matlab2023时遇到了一个问题: 按照网上的安装教程成功安装 在打开软件时 界面闪一下就消失 无法打开 但是 任务管理器显示matlab在运行中 解决方法如下: matlab快捷方式–>右键打开属性–>目标 填写许可证文件路径 D:\MATLAB\MatlabR20…...
Ubuntu Linux 文件、目录权限问题
本文为Ubuntu Linux操作系统- 第五弹 此文是在上期文件目录的内容操作基础上接着讲权限问题 上期回顾:Ubuntu Linux 目录和文件的内容操作 文件访问者身份与文件访问权限 Linux文件结构 所有者(属主)所属组(属组)其他…...
LabVIEW密码保护与反编译的安全性分析
在LabVIEW中,密码保护是一种常见的源代码保护手段,但其安全性并不高,尤其是在面对专业反编译工具时。理论上,所有软件的反编译都是可能的,尽管反编译不一定恢复完全的源代码,但足以提取程序的核心功能和算法…...
yolo11经验教训----之一
一、格式转换 可以把python中的.pt文件,导出为libtorch识别的格式: model YOLO("yolo11n.pt") model.export(format"torchscript") 二、查看结构 在c中,我用qt,这样做的: #include "…...
异步处理优化:多线程线程池与消息队列的选择与应用
目录 一、异步处理方式引入 (一)异步业务识别 (二)明确异步处理方式 二、多线程线程池(Thread Pool) (一)工作原理 (二)直面优缺点和适用场景 1.需要快…...
Hadoop生态圈框架部署 伪集群版(一)- Linux操作系统安装及配置
文章目录 前言一、下载CentOS镜像1. 下载 二、创建虚拟机hadoop三、CentOS安装与配置1. 安装CentOS2. 配置虚拟网络及虚拟网卡2.1 配置虚拟网络2.2 配置虚拟网卡 3. 安装 SSH 远程连接工具 FinalShell3.1 简介3.2 下载和安装3.2.1 下载3.2.2 安装 3.3 查看动态ip地址3.4 使用Fi…...
Go的Gin比java的Springboot更加的开箱即用?
前言 隔壁组的云计算零零后女同事,后文简称 云女士 ,非说 Go 的 Gin 框架比 Springboot 更加的开箱即用,我心想在 Java 里面 Springboot 已经打遍天下无敌手,这份底蕴岂是 Gin 能比。 但是云女士突出一个执拗,非我要…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...


















