news 2026/4/23 13:10:43

再探二分查找

作者头像

张小明

前端开发工程师

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/4/17 21:48:40

构建软件质量防线:测试缺陷的系统性预防措施

在快速迭代的软件开发环境中&#xff0c;缺陷发现得越晚&#xff0c;修复成本就呈指数级增长。研究表明&#xff0c;生产环境中发现的缺陷其修复成本是编码阶段发现的100倍以上。因此&#xff0c;现代软件测试已从单纯的缺陷检测向缺陷预防演进&#xff0c;致力于在缺陷产生前构…

作者头像 李华
网站建设 2026/4/23 22:35:03

构建高效可持续的自动化测试框架:从架构设计到落地实践

随着敏捷开发与DevOps实践的普及&#xff0c;自动化测试已成为保障软件质量的核心环节。2025年的今天&#xff0c;现代应用架构正朝着微服务、云原生和智能化的方向演进&#xff0c;这对测试框架的适应性、可维护性和扩展性提出了更高要求。一个设计良好的自动化测试框架不仅能…

作者头像 李华
网站建设 2026/4/18 19:34:35

自动化测试维护策略:构建可持续的测试资产

随着敏捷开发和DevOps实践的普及&#xff0c;自动化测试已成为软件质量保障的核心环节。然而&#xff0c;许多团队在享受自动化测试带来的效率提升时&#xff0c;却面临着测试用例老化、维护成本飙升、ROI持续下降的困境。据统计&#xff0c;超过60%的自动化测试失败并非源于产…

作者头像 李华
网站建设 2026/4/22 5:58:14

二叉树的相关知识以及代码实现(Java)

一、二叉树的定义与基本概念二叉树是一种非线性数据结构&#xff0c;每个节点最多包含 2 个子节点&#xff08;左子节点、右子节点&#xff09;&#xff0c;核心特点&#xff1a;&#xff08;1&#xff09;每个节点的子树数量不超过 2&#xff1b;&#xff08;2&#xff09;左、…

作者头像 李华