news 2026/8/8 0:45:20

再探二分查找

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
再探二分查找

各位好久不见,不知不觉2025都快要结束了,是时候来再总结一次算法(入门)的经验了。


最近笔者看标准算法库时,注意到C++算法库中只有两种二分查找的方法:lower_bound和upper_bound,分别用来查找第一个大于等于某个值的元素和第一个大于某个值的元素。

只有“第一个大于等于”和“第一个大于”,够用吗?

笔者想起了之前关于二分查找题的总结,似乎都是错题总结,而且条理很不清晰,根本没有道出二分查找使用的真相。既然标准库只提供了这俩,说明对于程序员的日常开发来讲,有这两个二分查找是完全够用的。那些之前的技巧和花招只能说是花拳绣腿,可以说都是这两种二分查找的变体。

1、查找第一个大于等于目标值的元素的位置

为了更好的理解二分查找干了什么,我想把这个过程描述为“Left指针要跳过哪些元素”,这是我们写对二分查找的关键。

从我为数不多的算法题经验来看,写二分查找的人分为两派:“Left结果派”和“Update结果派”,Left派习惯在循环结束后让left直接就是所寻找的目标值位置,而Update派习惯在二分过程中不断更新至正确答案。我是觉得Left派更好懂,而且结构清晰,有对称美。

如下,查找第一个大于等于目标值的元素。

int left = 0; int right = size()-1; //left派在left=right时跳出循环 while(right > left){ int mid = (left + right)/2; //什么时候移动left?target在mid的右边 if(target > nums[mid]){ //跳过所有的小于目标值的元素,那最终就是第一个大于等于目标值的元素 left = mid+1; //二分查找法的左指针必须要移动mid+1! } else{ right = mid; } } return left;

二分查找必须将left移动为mid+1。

如果保持left仅代替mid不移动的话,由于mid向左偏移的特性势必让算法出现死循环的情况(left在right的前一位)。而在(nums[mid]<target)条件下移动至mid+1位置,代表“left要跳过所有小于目标值的元素”,所以最终left只会落在第一个大于等于目标值的元素的位置。

除非是人为将mid的特性改为向右偏移,此时是right必须靠向left来杜绝死循环。

while(right > left){ int mid = (left + right)/2+1; //特性改为向右偏移 //什么时候移动left?target在mid的右边 if(target >= nums[mid]){ left = mid; } else{ right = mid-1; } } return left;

那这个查找到的就是最后一个小于等于目标值的元素,相当于是第一个大于等于目标值元素的左右对称算法。此时我在往期图文已有记载,但我根本记不住!这么麻烦!

但其实最后一个小于等于目标值的位置,就是“第一个大于目标值的位置 - 1)”。根本不用记那么多,只用会写开头说的那两种就好,这就是标准库的高明之处。

2、查找第一个大于目标值的元素的位置

刚刚说了二分查找的关键就是“Left指针要跳过哪些元素”,那查找第一个大于目标值的元素的位置就是“让Left指针跳过所有小于等于目标值的元素”。

如下,查找第一个大于目标值的元素。

int left = 0; int right = size()-1; //left派在left=right时跳出循环 while(right > left){ int mid = (left + right)/2; //什么时候移动left?target在mid的右边 if(target >= nums[mid]){ //跳过所有小于等于目标值的元素,那最终就是第一个大于目标值的元素 left = mid+1; } else{ right = mid; } } return left;

我们观察发现,两种查找的区别只在于Left指针跳跃的条件。仍然是保证了永远是Left指针移动至mid+1,right指针移动至mid。他们之间的不同仅仅在于要跳过的元素特性不同。在(nums[mid]<=target)条件下移动至mid+1位置,就是代表“left要跳过所有小于等于目标值的元素”,那就是第一个大于目标值的元素。

同理,当人为改变特性为向右偏移,是查找最后一个小于目标值的元素。相当于是和第一个大于目标值元素的左右对称算法。

while(right > left){ int mid = (left + right)/2+1; //什么时候移动left?target在mid的右边 if(target > nums[mid]){ left = mid; } else{ right = mid-1; } } return left;

这很麻烦,额外需要记住right往左边跳的情况,还要在二分时加上1,还要right跳到mid-1。所以我们可以直接是找到第一个大于等于目标值的元素位置后减去1,得到最后一个小于目标值的元素。

3、查找最后一个小于目标值的元素的位置

没错就是(第一个大于等于目标值的位置 - 1)。

int left = 0; int right = size()-1; //left派在left=right时跳出循环 while(right > left){ int mid = (left + right)/2; //什么时候移动left?target在mid的右边 if(target > nums[mid]){ //跳过所有的小于目标值的元素,那最终就是第一个大于等于目标值的元素 left = mid+1; //二分查找法的左指针必须要移动mid+1! } else{ right = mid; } } return left-1;

4、查找最后一个小于等于目标值的元素的位置

没错就是(第一个大于目标值的位置 - 1)。

int left = 0; int right = size()-1; //left派在left=right时跳出循环 while(right > left){ int mid = (left + right)/2; //什么时候移动left?target在mid的右边 if(target >= nums[mid]){ //跳过所有小于等于目标值的元素,那最终就是第一个大于目标值的元素 left = mid+1; } else{ right = mid; } } return left-1;

而上述的几种查找基本上可以覆盖所有常规的二分查找(默认从小到大排序)。

但是需要注意的是,上述情况均没有考虑目标值在数组范围外的情况。如果要考虑目标值在数组范围外的情况,直接用lower_bound或者upper_bound,最终left是数组的左边界或右边界。此时完全可以根据left的位置和left位置的值是否满足条件进行剪枝,可尝试LeetCode.74。

5、总结

综上所述:对于查找第一个大于等于目标值的元素和查找第一个大于目标值的元素只有一个要点——left每次必须跳到mid+1。而其余的两种变体完全可以通过这两种查找减一获得。遇到更复杂的变体只需考虑——我的left要跳过哪些元素,就都可以迎刃而解

至于两种标准库算法。

1、lower_bound

lower_bound(nums.begin(), nums.begin(), value)

返回第一个大于等于value的元素的迭代器,因为是跳过所有小于目标值的元素,写nums[mid]<target时,left=mid+1。

2、upper_bound

upper_bound(nums.begin(), nums.begin(), value)

返回第一个大于value的元素的迭代器,因为是跳过所有小于等于目标值的元素,写nums[mid]<=target时,left=mid+1。

为啥要叫这两个名字?lower_bound是第一个大于等于,比upper_bound的位置要lower一些,所以叫lower,你细品。


最近事务繁重,时常抽不出身。希望本文能给像我一样的小白一些帮助,祝福各位在2025剩下的时间里事事顺遂。

那么晚安喵~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/7 1:08:58

SQL约束解析

约束分类:NOT NULL 非空约束:字段必须有值UNIQUE 唯一约束:值不能重复&#xff0c;但允许多个 NULLPRINARY KEY 主键约束:既是 NOT NULL 又是 UNIQUEDEFAULT 默认约束: 保存数据时.如果未指定该字段的值,则采用默认值CHECK 检查约束:保证字段满足某一个值FOREIGN KEY 外键约束…

作者头像 李华
网站建设 2026/8/7 16:06:58

地铁调研12-17

今天地铁调研主要内容包括&#xff1a;1.跟随工人使用道尺进行巡检。主要测量内容&#xff1a;轨道内距&#xff0c;轨道水平情况。记录&#xff1a;/-x&#xff0c;毫米。2.涂油板&#xff08;道岔变轨部分&#xff09;的油是否还有。3.扣配件的螺栓是否松动扣配件的情况&…

作者头像 李华
网站建设 2026/8/6 20:33:54

现代软件测试工具全景对比与选型指南

随着敏捷开发与DevOps实践的普及&#xff0c;软件测试工具生态呈现百花齐放态势。截至2025年末&#xff0c;测试工具已从简单的BUG记录工具发展为覆盖自动化测试、性能监控、安全检测的完整解决方案。本文将通过功能性对比、适用场景分析及成本效益评估三个维度&#xff0c;为测…

作者头像 李华
网站建设 2026/8/7 0:55:33

基于 Apache POI 的体检报告 Word 生成实战文档

基于 Apache POI 的体检报告 Word 生成实战文档一 项目目标与总体设计 目标&#xff1a;基于模板快速生成排版规范的体检报告&#xff0c;支持文本替换、动态表格、图片插入&#xff0c;并可一键导出 PDF 用于归档与打印。技术选型&#xff1a; Apache POI XWPF&#xff1a;操作…

作者头像 李华
网站建设 2026/8/7 19:32:53

org.jetbrains.annotations的@Nullable 学习

Nullable 是 JetBrains 提供的一套用于 Java 静态分析的注解&#xff08;annotations&#xff09;之一&#xff0c;属于 org.jetbrains.annotations 包。它主要用于标注一个变量、参数、方法返回值等可能为 null&#xff0c;从而帮助 IDE&#xff08;如 IntelliJ IDEA&#xff…

作者头像 李华
网站建设 2026/8/7 19:33:20

计算机毕业设计springboot计算机硬件自配系统 基于Spring Boot的计算机硬件配置管理系统设计与实现 Spring Boot架构下的计算机硬件自选系统开发

计算机毕业设计springboot计算机硬件自配系统839019 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。随着信息技术的飞速发展&#xff0c;计算机硬件市场的复杂性和多样性不断增加…

作者头像 李华