算法概念速查
二分查找边界怎么写不死循环?开闭区间与找第一个/最后一个
一句话定义
二分查找的真正难点不是「找到等于 target 的位置」,而是找边界:第一个大于等于 target 的位置(左边界)、最后一个小于等于 target 的位置(右边界)。写对边界的钥匙是把「循环不变量」说出口——答案一定在当前区间里,每轮收缩都必须保住这句话,并且让区间实实在在变小。
先打个比方
像猜数字游戏的高手:每次猜正中间,对方说「大了」就扔掉右半边、「小了」扔掉左半边,100 万个数最多 20 次锁定。找边界版的区别在于:猜中了也不喊停——比如找第一个等于 target 的位置,命中后还要继续往左半边找,看有没有更靠前的。
它是怎么运作的
推荐一套「闭区间」写法并坚持到底:l=0、r=n−1,while (l <= r),mid 比较后 l = mid + 1 或 r = mid − 1。因为 mid 已经比较过、确定不是答案(或已被记录),±1 把它排除出区间——这正是不死循环的保证。找左边界时命中不返回,记下 mid 后继续 r = mid − 1 往左压。
死循环的病根只有一个:区间没有变小。典型写法 l = mid(不加一)在区间剩两个元素时,mid 恒等于 l,一轮过后区间原地踏步,永远转圈。用开区间写法(while (l < r) 配 r = mid)也能自洽,但两种模板别混着用——开闭区间和收缩方式必须配套。
更进一步是「二分答案」:答案空间单调时(吃香蕉速度越快越能按时吃完),对答案本身二分,把「求最小可行值」转成「判定 mid 可不可行」。套的还是找左边界的模板。
什么时候用:识别信号
题目里出现下面任何一条,就该想到「二分查找边界」。
- 有序数组上找目标的插入位置、第一次 / 最后一次出现的位置
- 「第一个满足 / 最后一个不满足」某单调条件的位置(第一个错误的版本)
- 「最小化最大值 / 最大化最小值」——二分答案的标准句式
- 旋转有序、山峰数组等局部有序结构,靠比较 mid 与端点决定去哪半
别和它们搞混
vs 朴素二分(找存在)
找存在命中即返回;找边界命中不停手,记录候选后继续向目标方向压缩区间。lower_bound / upper_bound 干的就是这件事,手写时把「命中后往哪继续」想清楚。
vs 双指针
都吃单调性:二分每轮砍一半,O(log n),适合「查一个位置」;双指针线性推进,O(n),适合「枚举所有配对」。别在该二分的题上写 O(n) 扫描,也别硬用二分做配对枚举。
配套动画题:眼见为实
每道题都有逐步交互动画,看着数据动起来,比读十遍文字都快。
常见追问
mid 为什么写成 l + (r - l) / 2?
数学上和 (l + r) / 2 相等,但 l + r 在两者都很大时可能超出 int 上限溢出。先算差再加永不溢出,是工程标准写法,面试写出来是加分细节。
while (l < r) 和 while (l <= r) 哪个对?
都对,前提是和区间定义配套:闭区间 [l, r] 用 l <= r 配 mid±1;左闭右开 [l, r) 用 l < r 配 r = mid。混搭(比如 l <= r 配 r = mid)才是死循环和漏判的来源,选一套坚持到底。
怎么快速自查会不会死循环?
拿两个元素的区间手推一轮:l=3、r=4 时 mid=3,看你的收缩分支是否让 l 或 r 真的动了。任何一个分支跑完区间原样不动,就是死循环。