【算法】二分查找(上)
目录
一、写好二分查找的四个步骤
二、在排序数组中查找元素的第一个和最后一个位置
三、搜索插入位置
四、x的平方根
通过上篇文章【手撕二分查找】,我们知道了二分查找的【四要素】:初始值、循环条件、mid的计算方式、左右边界更新语句。
循环条件和左右边界更新语句,决定了二分查找循环终止时left和right的位置(相错/相等/相邻)
初始值和mid的计算方式,要使得中间下标mid覆盖且仅覆盖【搜索空间】中所有可能的下标
一、写好二分查找的四个步骤
1.确定区间形式(【左闭右闭】、【左闭右开】、【左开右开】...)
2.维护区间形式:为了维护区间形式,要设计初始值、循环条件、左右边界。
满足:
·初始值:左右边界初始值的区间覆盖整个数组
·循环条件:满足区间形式的特点而设。如:【左闭右闭】在两者相错时,终止循环。所以循环条件为:left<=right。【左闭右开】在两者相遇时,终止循环。所以循环条件为:left<right
·左右边界:在每次搜索完后,需要排除已经搜索过的区间。
3.选择mid的计算方式:mid计算方式的选择,是为了帮助循环缩小区间,避免死循环。
通常都是采取向下调整,但是有时候需要向上调整。
如:在【左开右闭】中(left=mid、right=mid-1),若采取向下调整,mid会在中间两个元素中选择较小的那一个位置,当左右指针相邻时,mid始终选择较小的,最终由于left=mid,导致死循环。
解决方法,就是保证在左右指针相邻时,让mid选择 能够帮助缩小区间的(移动右指针)--向上调整
总结:
根据左右边界的更新语句,让mid的计算方式 在左右指针相邻时,选择能够帮助缩小区间的一个(即:指针不指向mid)。
向上调整:mid会选择中间两个元素中较大的(nums[mid]>target)---移动右指针【左开右闭】
向下调整:mid会选择中间两个元素中较小的(nums[mid]<target)---移动左指针【左闭右开】
注意:【左闭右闭】、【左开右开】都是向下调整
上述三个步骤以及满足了二分查找的【四要素】,但是我们还需要注意返回值
4.返回值:通过分析数组中元素的三种情况(都大于、存在、都小于target)以及其对于的返回值,判断应该返回什么(最好是画图)
如:【左闭右闭】中,找出大于等于target的数字下标

考虑nums中元素的三种情况:
1.nums中所有元素都小于target时,right不更新,最终left=nums.length,因此当这个关系成立时,返回-1
2.nums中存在元素大于等于target时,由【循环不变】的关系可知,最终应该返回left
3.nums中所有元素都大于target时,left不更新,left=0,最终right=-1,此时应当返回下标0,因此返回left
二、在排序数组中查找元素的第一个和最后一个位置
题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
题目描述:
给你⼀个按照⾮递减顺序排列的整数数组 nums,和⼀个⽬标值 target。请你找出给定⽬标值在数组
中的开始位置和结束位置。
如果数组中不存在⽬标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
⽰例 1:
输⼊:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
⽰例 2:
输⼊:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
⽰例 3:
输⼊:nums = [], target = 0
输出:[-1,-1]
提⽰:
0 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
nums 是⼀个⾮递减数组
-10^9 <= target <= 10^9
题目分析:
我们需要在非递减数组中,找到目标值的开始位置和结束位置。只有当目标值在数组中至少存在一个时,才会有开始位置或结束位置。其余情况返回[-1,-1]
我们这里使用两次二分查找分别找开始位置(Search1)和结束位置(Search2)
Search1:我们只需要在数组中找到第一个大于等于target的数字下标,便可以表示开始位置
Search2:可以在数组中找到第一个大于target的数字下标,然后令其减1,即可表示结束位置
但是在求开始位置时,有一种情况无法求出。即:当数组中没有target时,Search1返回的数字下标是第一个大于target的数字下标
而Search2求的也是第一个大于target的数字下标。因为Search2最终减1,导致两指针相错.所以当两指针相错时,表示没有target,返回[-1,-1]
解题思路:
1.确定区间形式:我们这里选择【左闭右闭】
2.维护区间形式:初始值、循环条件、左右边界
初始值:left=0,right=nums.length-1;
循环条件:left<=right
左右边界:left=mid+1,right=mid-1
3.选择mid的计算方式:向下调整:mid=left+(right-left)/2
4.返回值:return left==nums.length?-1:left(Search1)、return left(Search2)--解释如下
注意:在这四个步骤中,第4步尤其需要注意,因为前三步很容易得出,但返回值需要考虑数组中元素的三种情况。
对于Search1:寻找第一个大于等于target的数字下标

考虑nums中元素的三种情况:
当数组中所有元素都小于target时,right不更新,最终left=nums.length。但此时应该返回-1
当数组中存在元素大于等于target时,按【循环不变】的关系,应该返回left
当数组中所有元素都大于等于target时,left不更新(0),最终right= -1,此时应该返回left(0)
当数组中最后一个元素等于target时,left=nums.length-1,right=left-1,此时返回left
对于Search2:寻找的是第一个大于target的数字下标

考虑nums中元素的三种情况:
当数组中所有元素都小于等于target时,right不更新,最终left=nums.length。但此时应该返回-1
当数组中存在元素大于target时,按【循环不变】的关系,应该返回left
当数组中所有元素都大于target时,left不更新(0),最终right= -1,此时应该返回left(0)
需要注意的是:
当数组最后一个元素(等于target)是结束位置时,此时left=nums.length,在减1后就表示数组最后一个元素。这里不能因为left=nums.length,就返回-1。所以我们的返回语句使用的是return left。这里的最后一个元素包含了数组只有一个元素的情况。
所以我们需要处理:当数组中所以元素都小于target时,返回 -1的情况
解题代码:
class Solution {public static int[] searchRange(int[] nums, int target) {if(nums.length==0)return new int[]{-1,-1};int first=search1(nums,target);//找到第一个大于等于target的数字下标int end=search2(nums,target);//找到第一个大于target的数字下标//处理search2中,数组最后一个元素小于target的情况if(end>=1&&nums[end-1]<target)return new int[]{-1,-1};if(first<=end-1) return new int[]{first,end-1};else return new int[]{-1,-1};}//找到第一个大于等于target的数字下标private static int search1(int[] nums, int target) {int left=0;int right=nums.length-1;while(left<=right){int mid=left+(right-left)/2;if(nums[mid]<target)left=mid+1;//注意这里是 <else right=mid-1;}return left==nums.length?-1:left;}//找到第一个大于target的数字下标private static int search2(int[] nums, int target) {int left=0;int right=nums.length-1;while(left<=right){int mid=left+(right-left)/2;if(nums[mid]<=target)left=mid+1;//注意这里是 <=else right=mid-1;}return left;}
} 
 
我们这里分别使用两次二分查找,一方面是为了让大家可以熟悉二分查找大致流程,另一方面可以让大家理解其中的不同之处。
当然还有很多其他题解:如:我们在数组中找到任意一个target,然后再左右移动找到边界即可。
三、搜索插入位置
题目链接:35. 搜索插入位置 - 力扣(LeetCode)
题目描述:
给定⼀个排序数组和⼀个⽬标值,在数组中找到⽬标值,并返回其索引。如果⽬标值不存在于数组中,返回它将会被按顺序插⼊的位置。请必须使⽤时间复杂度为 O(log n) 的算法。⽰例 1:输⼊: nums = [1,3,5,6], target = 5输出: 2⽰例 2:输⼊: nums = [1,3,5,6], target = 2输出: 1⽰例 3:输⼊: nums = [1,3,5,6], target = 7输出: 4
题目分析:题目要求返回目标值在数组中的插入位置。其实就是查找第一个大于等于target的数字下标。又因为时间复杂度为O(log n) ,我们果断使用二分查找
在上一个题目,我们使用了【左闭右闭】的区间形式,在本题,我们使用【左闭右开】。只是为了让大家熟悉每种区间形式的写法
四步骤
1.确定区间形式:【左闭右开】
2.维护区间形式:初始值、循环条件、左右边界
初始值:left=0,right=nums.length
循环条件:left<right
左右边界:left=mid+1,right=mid
3.选择mid的计算方式:向下调整:mid=left+(right-left)/2
4.返回值:return left(如下)

考虑nums中元素的三种情况:
1.nums中所有元素都小于target时,right不更新,最终left=right=nums.length,因此当这个关系成立时,返回left/right
2.nums中存在元素大于等于target时,由【循环不变】的关系可知,最终可以返回left/right(相遇)
3.nums中所有元素都大于等于target时,left不更新,left=0,最终right=left=0,此时应当返回下标0,因此返回right/left
解题代码:
public static int searchInsert(int[] nums, int target) {//区间形式:【左闭右开】--据此设计初始值,循环条件,左右边界,mid计算方式int left=0;int right=nums.length;//初始值while(left<right){//循环条件int mid=left+(right-left)/2;//向下调整if(nums[mid]<target)left=mid+1;//边界设计else right=mid;}return left;} 
 
 
四、x的平方根
题目链接:69. x 的平方根 - 力扣(LeetCode)
题目描述:
给你⼀个⾮负整数 x ,计算并返回 x 的 算术平⽅根 。由于返回类型是整数,结果只保留 整数部分 ,⼩数部分将被 舍去 。注意:不允许使⽤任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。⽰例 1:输⼊: x = 4输出: 2⽰例 2:输⼊: x = 8输出: 2解释:8 的算术平⽅根是 2.82842... , 由于返回类型是整数,⼩数部分将被舍去。提示:
0 <= x <= 2^31 - 1 (2,147,483,647)
题目分析:只需要在一段区间(可以是[0,根号x])内找到一个数t,满足 t*t<=x,(t+1)*(t+1)>x
其实也就是在区间内,找到第一个数 t,使其的平方 小于等于x即可
这个区间内的数是有序的,直接使用二分查找
解法1(左闭右开)
1.确定区间形式:【左闭右开】
2.维护区间形式:初始值、循环条件、左右边界
初始值:left=0,right=x/2+1;//由于根号x<=x/2(当x不等于0或1时),并且因为右侧边界为开,所以我们这里右侧边界设为x/2+1
这里将右侧边界设为x/2+1,还有一个好处,不需要额外处理x=0或x=1的情况
注意:我们只需要保证初始值的范围覆盖了整个【搜索空间】即可。在这里满足right*right>=x
循环条件:left<right
左右边界:left=mid+1,right=mid
3.选择mid的计算方式:向下调整:mid=left+(right-left)/2
4.返回值:return left;//但本题需要额外处理
额外处理:
我们知道当left=right时,循环终止。

在本题中,需要注意,当left向右侧移动时(mid*mid<x-->left=mid+1),若此时left与right相遇,循环终止,mid无法更新。我们判断了(mid*mid<x),但是返回的left=mid+1,我们还需要额外判断新的mid(在循环外侧,再计算一次mid)处的平方是否大于x,如果大于x,返回的应该是left-1
注意:由于本题0 <= x <= 2^31 - 1 (2,147,483,647),mid*mid可能会超出int类型,需要转换为long类型
解题代码:
class Solution {public static int mySqrt(int x) {//左闭右开//if(x==0||x==1)return x;int left=0;int right=x/2+1;//这里处理了x=0或x=1的情况int mid=0;while(left<right){mid=left+(right-left)/2;//这里不再是下标,而是中间值if((long)mid*mid<x)left=mid+1;else right=mid;}mid=left+(right-left)/2;if((long)mid*mid>x)return left-1;return left;}
} 
 
解法2(左开右开)
使用【左开右开】的区间形式解决本题,有一个好处:因为左右边界都移动到mid处,当mid*mid<=x时,left=mid;否则right=mid。当left+1=right时,循环终止,left*left<=x,right*right>x
这时,可以直接返回left,不需要额外判断 新的mid处的平方是否大于x。
四步骤
1.确定区间形式:【左开右开】
2.维护区间形式:初始值、循环条件、左右边界
初始值:left=0,right=x/2+2//注意我们这里设置right=x/2+2,因为当x等于0或者1时,我们让(left,right)=(0,2),这样仍然能进入循环(left+1<right),得出最终结果。而这里left设置为-1或0都可以。只需要保证当特殊情况x=0/x=1时,仍然能进入循环即可
循环条件:left+1<right
左右边界:left=mid,right=mid
3.选择mid的计算方式:向下调整:mid=left+(right-left)/2
4.返回值:return left
解题代码:
class Solution {public static int mySqrt(int x) {//左开右开int left=0;//-1也可以int right=x/2+2;//重点while(left+1<right){int mid=left+(right-left)/2;//这里不再是下标,而是中间值if((long)mid*mid<=x)left=mid;else right=mid;}return left;}
} 
相关文章:
【算法】二分查找(上)
目录 一、写好二分查找的四个步骤 二、在排序数组中查找元素的第一个和最后一个位置 三、搜索插入位置 四、x的平方根 通过上篇文章【手撕二分查找】,我们知道了二分查找的【四要素】:初始值、循环条件、mid的计算方式、左右边界更新语句。 循环条件…...
【人工智能】GPT-4 vs DeepSeek-R1:谁主导了2025年的AI技术竞争?
前言 2025年,人工智能技术将迎来更加激烈的竞争。随着OpenAI的GPT-4和中国初创公司DeepSeek的DeepSeek-R1在全球范围内崭露头角,AI技术的竞争格局开始发生变化。这篇文章将详细对比这两款AI模型,从技术背景、应用领域、性能、成本效益等多个方…...
linux nginx 安装后,发现SSL模块未安装,如何处理?
?? 主页: ?? 感谢各位大佬 点赞?? 收藏 留言?? 加关注! ?? 收录于专栏:运维工程师 文章目录 前言SSL模块安装 前言 nginx 安装后,发现SSL模块未安装,如果不需要配置SSL域名,就无关紧要。但是很多时候客户后…...
蓝桥杯 - 每日打卡(类斐波那契循环数)
题目: 解题思路: 假设输入数值为number 分析题目,如果想要解决这个问题,我们需要实现两个方法,第一个检查number是否是类斐波那契,第二个是模拟1e7 - 0的过程,因为是求最大的,那么我们从1e7开始…...
深入探索C++17文件系统库:std::filesystem全面解析
前言 在C编程中,文件系统操作是许多应用程序的基础功能之一。无论是读写文件、创建目录,还是遍历文件系统,文件系统操作几乎无处不在。然而,在C17之前,标准库并没有提供一个统一、高效且易用的文件系统操作接口。开发…...
LLM | 论文精读 | GIS Copilot : 面向空间分析的自主GIS代理
论文标题:GIS Copilot: Towards an Autonomous GIS Agent for Spatial Analysis 作者:Temitope Akinboyewa,Zhenlong Li,Huan Ning,M. Naser Lessani等 来源:arXiv DOI:10.48550/arXiv.2411.…...
Unity 适用Canvas 为任一渲染模式的UI 拖拽
RectTransformUtility-ScreenPointToWorldPointInRectangle - Unity 脚本 API 将一个屏幕空间点转换为世界空间中位于给定RectTransform 平面上的一个位置。 实现 获取平面位置。 parentRT transform.parent as RectTransform; 继承IPointerDownHandler 和IDragHandler …...
基于遗传算法的无人机三维路径规划仿真步骤详解
基于遗传算法的无人机三维路径规划仿真步骤详解 一、问题定义 目标:在三维空间内,寻找从起点到终点的最优路径,需满足: 避障:避开所有障碍物。路径最短:总飞行距离尽可能短。平滑性:转折角度不宜过大,降低机动能耗。输入: 三维地图(含障碍物,如立方体、圆柱体)。起…...
windows下使用Hyper+wsl实现ubuntu下git的平替
文章目录 前言一、安装Hyper、wsl1. 安装Hyper2. 安装wsl 二、配置Hyper三、安装并使用git总结 前言 众所周知,Ubuntu下安装git只需执行sudo apt install git即可使用默认终端拉取代码,但是Windows上使用git既没有linux便捷,又没有MacOS优雅…...
基于Java+SpringCloud+Vue的前后端分离的房产销售平台
基于JavaSpringCloudVue的前后端分离的房产销售平台 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末附源码下载链接&#x…...
以影像技术重构智能座舱体验,开启驾乘互动新纪元
在汽车智能化浪潮席卷全球的今天,座舱体验早已突破传统驾驶功能的边界,成为车企竞争的核心赛道。美摄科技凭借其在图像处理与AI算法领域的深厚积累,推出全链路智能汽车图像及视频处理方案,以创新技术重新定义车载影像系统…...
deepseek在pycharm 中的配置和简单应用
对于最常用的调试python脚本开发环境pycharm,如何接入deepseek是我们窥探ai代码编写的第一步,熟悉起来总没坏处。 1、官网安装pycharm社区版(免费),如果需要安装专业版,需要另外找破解码。 2、安装Ollama…...
LLM大型语言模型(一)
1. 什么是 LLM? LLM(大型语言模型)是一种神经网络,专门用于理解、生成并对人类文本作出响应。这些模型是深度神经网络,通常训练于海量文本数据上,有时甚至覆盖了整个互联网的公开文本。 LLM 中的 “大” …...
尚庭公寓项目记录
数据库准备 保留图像时,保存图像地址就可以数据表不是越多越好,可以用中间表来实现俩个表之间的联立这样方便查数据但是却带来性能问题而减少表的jion但是提高性能,以冗余来换去性能采用MySQL,InnoDB存储引擎物理删除和逻辑删除逻…...
飞算JavaAI编程工具集成到idea中
AI插件介绍 飞算AI的插件下载地址,里边也有安装步骤: JavaAI 以上图是不是看着很牛的样子,一下成为高手确实说的太夸张了点, 一键生成后端JavaWeb项目还是挺方便的。 飞算JavaAI插件安装 Idea->>file->>setting-&…...
【每日八股】计算机网络篇(二):TCP 和 UDP
目录 TCP 的头部结构?TCP 如何保证可靠传输?1. 确认应答机制2. 超时重传3. 数据排序与去重4. 流量控制5. 拥塞控制6. 校验和 TCP 的三次握手?第一次握手第二次握手第三次握手 TCP 为什么要三次握手?问题一:防止历史连接…...
课程《MIT Introduction to Deep Learning》
在Youtubu上,MIT Introduction to Deep Learning (2024) | 6.S191 共8节课: (1) MIT Introduction to Deep Learning (2024) | 6.S191 (2) MIT 6.S191: Recurrent Neural Networks, Transformers, and Attention (3) MIT 6.S191: Convolutional Neural N…...
GCC RISCV 后端 -- C语言语法分析过程
在 GCC 编译一个 C 源代码时,先会通过宏处理,形成 一个叫转译单元(translation_unit),接着进行语法分析,C 的语法分析入口是 static void c_parser_translation_unit(c_parser *parser); 接着就通过类似递…...
UI组件库及antd
什么是UI组件库及antd安装 随着商业化的趋势,企业级产品中需求多且功能复杂,且变动和并发频繁,常常需要设计者与开发者快速做出响应,同时这类产品中有很多类似的页面及组件,可以通过抽象得到一些稳定且高复用性的内容…...
Windows下使用ShiftMediaProject方法编译FFmpeg
Windows SDK 8.1版本不支持dxva vp9! 需要10.0.17134.0!或者把config编译选项去掉 1.下载源码 https://github.com/ShiftMediaProject 2.创建ShiftMediaProject文件夹 把下载好的源码放入source 3.进入SMP执行 project_get_dependencies.bat 自动下载ffmepg依赖项…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
