当前位置: 首页 > news >正文

二分查找与二分答案:在有序世界里“耍流氓”的高效算法

一、二分查找基础:别再从头一个个数了
1.1 二分查找的概念与原理:像个心机的“猜数字”玩家

在计算机科学的江湖里,二分查找就像是一个“心机Boy”。它绝不干那种从头开始傻乎乎挨个数的体力活,而是专门在有序数组这个规矩的圈子里,用最少的步骤精准KO目标。

它还有个名字叫“折半查找”,听着挺高大上,其实就是“对半砍”。它的核心哲学是:既然队伍是排好序的,那我就直接跳到队伍中间,问一句:“喂,目标在你们那边还是这边?”

举个栗子,假设我们有一个有序数组 [1, 3, 5, 7, 9, 11, 13],我们要找 15。

普通人的做法是:1?不是。3?不是。5?不是……(累死)。
二分查找的做法是:直接站在中间(9的位置),大喊一声:“15在哪?”
因为 9 < 15,数组这头太小了,目标肯定在右边那群“高个子”里。于是,它直接把左边一半人“咔嚓”划掉,不管了。
接着在右半场 [11, 13] 中间站,再喊:“15在哪?”
13 还是小于 15,继续把 13 左边的(虽然没剩几个了)划掉。
最后发现没人了,得出结论:15 这家伙根本不在这个数组混。

这招数叫“剥洋葱”有点太温柔了,我觉得叫“切西瓜”更贴切。每次一刀下去,不要的那一半直接扔进垃圾桶,效率极高。相比于普通人 O(n) 的线性查找(数据多十倍,时间多十倍),二分查找是 O(logn) 的(数据多十倍,时间只多一点点),简直是算法界的“流氓”。

1.2 二分查找的实现步骤:别把边界写崩了

写二分查找代码,就像在走钢丝,一不小心就会“摔死”(死循环或数组越界)。

首先,我们要划定地盘:左边界 left = 0,右边界 right = n-1。

然后进入循环,只要 left <= right,就说明还有地盘可搜。

中间位置 mid 怎么算?你可能会写 (left + right) / 2。停!别这么干!如果 left 和 right 都是接近内存上限的大数,它们一加直接溢出变成负数,程序就崩了。我们要用点黑科技:mid = left + (right - left) / 2。这样先减后加,稳如老狗。

算出 mid 后,就拿 arr[mid] 和目标值比:

  • 如果相等,恭喜你,抓到了,直接 return。
  • 如果 arr[mid] < target,说明目标在右边,左边界收缩:left = mid + 1。
  • 如果 arr[mid] > target,说明目标在左边,右边界收缩:right = mid - 1。

很多新手在这里容易犯“手抖病”,写成 left = mid 或 right = mid。千万别!因为 mid 位置已经比过了,肯定不是目标,再包含它就是浪费时间,甚至导致死循环。一定要把比过的点“剔除”出去。

二、二分查找的应用场景:不仅仅是找数
2.1 在有序数组中查找元素:精准打击

还是那个数组 [1, 3, 5, 7, 9, 11, 13],找 7。

开局:left=0, right=6。
第一刀:mid=3, 值是 5。5 < 7,所以把 5 及其左边全扔了,left = 4。
第二刀:mid=5, 值是 11。11 > 7,把 11 及其右边全扔了,right = 4。
第三刀:mid=4, 值是 7。Bingo!

你看,7个数只用了3次就找到了。如果数组有一亿个数,二分查找最多也只需要找大约27次(因为 2^27 约等于一亿多)。这种效率,让暴力查找只能跪下唱《征服》。

2.2 寻找插入位置:给新来的找个座

有时候我们不需要找存在的数,而是想知道如果要把一个数插进去,应该插在哪,才能不破坏数组的有序性。

比如在 [1, 3, 5, 7, 9, 11, 13] 中插入 8。

按照二分逻辑:
mid=3, 值是 5。5 < 8,说明 8 应该在右边,left = 4。
mid=5, 值是 11。11 > 8,说明 8 应该在左边(但包含11的位置),right = 4。
此时 left=4, right=4。
mid=4, 值是 7。7 < 8,所以 left = mid + 1 = 5。
现在 left=5, right=4,循环结束。

注意!此时 left 的值就是插入点。因为当循环结束时,left 指向的是第一个比目标值大的位置。所以 8 应该插在下标 5 的位置(也就是 9 的前面),完美!

三、二分答案入门:当“猜数字”变成了“猜答案”
3.1 二分答案的定义与思想:暴力穷举的“作弊”版

如果说二分查找是在“有序的数组”里找数,那二分答案就是在“有序的答案范围”里找解。

它就像是一个考试只会在选择题里蒙答案的老油条。题目很难,不会做?没关系,只要知道答案肯定在 0 到 100 之间,而且具有单调性(比如猜大了就不行,猜小了就行),那就可以二分答案。

经典例子:切绳子。
有一堆绳子,长度分别是 [5, 8, 10],我要把它们切成 6 段,问每段最长能有多长?

普通人可能会从 1cm 开始试:切 1cm 能切很多段,行;切 2cm 也能行……一直试到切不动为止。这叫线性查找,太慢。
二分答案玩家会这样想:每段长度肯定在 0 到最长绳子(10)之间。
我先猜中间值 5cm。

  • 切 5cm:5/5=1段,8/5=1段,10/5=2段,总共 4 段。不够 6 段!说明切大了,得切小点。
    于是把右边界缩小到 5。
    再猜中间值 2cm。
  • 切 2cm:5/2=2段,8/2=4段,10/2=5段,总共 11 段。大于 6 段!说明切小了,可以试着切大点。
    于是把左边界扩大到 2。
    如此反复,很快就能逼近那个“刚刚好能切出6段”的最大长度。

核心逻辑是:如果 x 可行,那么所有比 x 小的都可行;如果 x 不可行,所有比 x 大的都不可行。利用这种单调性,我们就能二分。

3.2 二分答案适用的问题类型:当“暴力”太丑陋时

当你遇到以下类型的问题,且数据量很大(n 到 10^5 或更大)时,二分答案往往是救星:

  • “求最大值最小”或“求最小值最大”。比如“让最短的那段尽量长”。
  • “判断是否存在一个方案满足条件”,且方案具有单调性。
  • 传统的暴力枚举(O(n) 或 O(n^2))会超时,但验证一个猜测的复杂度很低(比如 O(n))。
四、二分答案的经典案例:从理论到实战
4.1 数的范围问题:寻找边界

在数组 [1, 2, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9] 中找 5 的起始和结束位置。

找起始位置(左边界):
我们二分查找,当 arr[mid] == 5 时,不要急着返回,因为可能左边还有。我们要把搜索范围强行拉到左边:right = mid - 1。
当循环结束时,left 通常就是第一个 5 的位置。

找结束位置(右边界):
同样二分,当 arr[mid] == 5 时,去右边找:left = mid + 1。
循环结束时,right 通常就是最后一个 5 的位置。

这叫“二分下界的技巧”,核心在于等于的时候不要停,要继续往左(或右)逼近,直到把边界“挤”出来。

4.2 木材加工问题:实数二分的尴尬

有一堆木材 [5, 8, 10, 12, 15, 18, 20],要切出 10 段等长的木头,求最长长度。

这和切绳子一样。但注意,长度可能是小数(比如 8.333米)。
这时候整数二分的 left <= right 就不好用了,因为实数是连续的。

我们需要设定一个精度,比如 1e-6(0.000001)。
循环条件变成:while (right - left > 1e-6)
中间值 x = (left + right) / 2。
验证 x 是否可行,调整边界。

最后输出 left 或 right 或它们的平均值即可。实数二分的关键在于控制好精度,别让计算机去死磕两个无限接近的数。

五、二分查找与二分答案的对比:表兄弟的异同
5.1 相同点与联系:换汤不换药

这俩货本质上是一回事,核心都是“分治”和“单调性”。
代码长得几乎一模一样:都有 left、right、mid,都有 while 循环,都要根据条件收缩边界。
时间复杂度都是 O(logn),都是效率极高的算法。

二分查找可以说是二分答案的一个特例:二分查找是在数组下标这个“有序序列”里二分;二分答案是在“解空间”里二分。

5.2 不同点与区别:找的东西不一样

二分查找找的是“存在的东西”。数组里必须有这个数,你才能找到它。
二分答案找的是“最优的东西”。这个答案可能在原数组里根本不存在,它是通过逻辑推导出来的。

二分查找的逻辑通常是:比较大小,相等就赢了。
二分答案的逻辑通常是:模拟验证,可行就往大了猜,不可行就往小了猜。

六、二分查找与二分答案的优化技巧:别让细节坑了你
6.1 边界条件的处理:死循环的元凶

写二分最怕死循环。比如在找左边界时,如果写成了 left = mid,当 left 和 right 相邻时,mid 算出来等于 left,赋值后 left 还是等于 mid,这就死循环了。
记住口诀:查找时,不包含 mid(+1/-1);收缩时,保留 mid(因为可能就是解)。

对于整数溢出,坚决使用 left + (right - left) / 2。

对于实数二分,不要用 == 判断,要用精度控制。

6.2 时间复杂度的优化:常数优化

虽然复杂度都是 O(logn),但能快一点是一点。
计算 mid 时,位运算比除法快:mid = left + ((right - left) >> 1)。虽然现代编译器通常会自动优化,但写出来显得你懂汇编。

如果验证一个猜测的代价很高(比如 O(n)),可以尝试优化验证函数的逻辑,或者利用数据结构(如前缀和)把验证复杂度降下来。

七、实际应用案例:这玩意儿真能赚钱
7.1 在数据库查询中的应用:索引的底层逻辑

为什么数据库给字段加了索引后查询飞快?因为索引(如B+树)底层就是利用了二分的思想。
没有索引,查100万条数据可能要扫全表。
有索引,查100万条数据大概只需要 20 次磁盘IO(log2(1000000) ≈ 20)。
这就是二分查找改变世界的地方。下次老板问你为什么要加索引,你就说这是“二分法的降维打击”。

7.2 在算法竞赛中的应用:上分神器

在ACM/ICPC或者LeetCode周赛中,二分答案是中等题和困难题的常客。
遇到“最大化最小值”这种绕口令一样的题目,别犹豫,直接套二分答案模板。
设定 low 和 high,写个 check 函数,然后二分。
只要 check 函数逻辑正确,这道题基本就稳了。这招被称为“二分答案大法好”,是竞赛选手从暴力选手进化成优雅选手的标志。

总之,二分查找和二分答案虽然初看有点绕,但一旦掌握了“单调性”和“边界处理”这两个核心,你会发现它们是算法世界里最简单、最暴力、最高效的工具。别再暴力 for 循环了,学会二分,你就是整条街最靓的仔!

http://www.jsqmd.com/news/831662/

相关文章:

  • SVG 与 VSCode:高效协同的图形编辑利器
  • 夺表查询
  • 【技术底稿 36】Docker Compose 微服务迁移 K3s:离线导入、镜像挂载、Nginx 重定向全踩坑复盘
  • 如何在Blender中完美导入Rhino 3dm文件:终极解决方案指南
  • 免费仿真分析报告生成实战指南
  • 会话管理利器:从JWT到Redis,构建安全可扩展的用户认证系统
  • 从零读懂RDMA的钥匙机制:硬件如何用L_Key/R_Key保护你的内存
  • PyWxDump:从微信数据管理工具到开源合规的深刻教训
  • 从零做了一个 AI 面试陪练工具,聊聊全过程
  • AI Agent在科学研究中的辅助作用
  • 【python基础】使用python下载二进制文件
  • LSM6DS3TR-C与磁力计九轴融合:嵌入式姿态解算算法实现与优化
  • AI如何学习科学品味:从论文评估到智能文献筛选的实践路径
  • 基于PIR传感器与HalloWing的自动惊吓陷阱:嵌入式系统交互实践
  • Rider对非商业用途免费全球最受喜爱的 .NET 和游戏开发 IDE
  • 动画性能监控:打造流畅的用户体验
  • 3分钟解决iPhone在Windows无法上网的终极方案:苹果USB网络共享驱动一键安装指南
  • codex features
  • 降AI率软件越便宜越好吗?实测5个主流降AI工具,首选嘎嘎降!
  • Solon框架解析:轻量级Java应用开发新范式与云原生实践
  • AWorksLP嵌入式开发:基于FatFs的SD卡文件系统操作全解析
  • 2026年当下,长治整屋定制优选平台深度解析与联系指南 - 2026年企业推荐榜
  • Arm Cortex-A处理器缓存与TLB架构深度解析
  • 2026 首发|GEO 全域运营经典案例:公域引流到私域转化全链路完整复盘
  • HAProxy 如何实现 TCP 模式下的 MySQL 数据库负载均衡
  • 基于NLP的文本逻辑分析工具:思考词汇识别与可视化实践
  • 4.AI大模型-幻觉、记忆、参数-大模型底层运行机制
  • 【mv】戏剧结构为什么要设计幕 起承转合 这种设计
  • Harness 中的请求标识染色:端到端追踪
  • 2026年5月河南桥梁护栏项目优选供应商实力解析 - 2026年企业推荐榜