前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库二分查找 循环不变量 边界写法
算法算法搜索与排序

写二分查找时用左闭右闭和左闭右开区间有什么本质区别?

两种区间写法的本质区别在于循环不变量不同:闭区间保证[L,R]内都是候选,退出时L=R+1;开区间保证搜索区间为[L,R),退出时L==R。这直接决定了while条件与mid的加减一操作。

前端进阶之旅 · 一题精讲更新于 2026.09.05
算法#搜索与排序#测试
先看核心答案读代码示例
理解线索

区间边界决定算法行为

  1. 闭区间[L,R]两个端点都是候选,mid被排除后需加一或减一
  2. 开区间[L,R)右端点不参与搜索,mid可能保留故right=mid
  3. 循环不变量每次迭代后性质不变,退出位置正是答案所在

当区间收窄到空或相等时,下标就是第一个满足条件或目标插入点

核心回答

先记住这个答案

二分查找的两种区间写法对应不同循环不变量。左闭右闭[L,R]中,搜索包含两端,比较后必须排除mid,故更新为L=mid+1或R=mid-1,循环继续条件是L<=R,结束时L=R+1。左闭右开[L,R)中,右边界不包含候选,若mid不满足条件则L=mid+1,否则R=mid,循环条件是L<R,结束时L==R,该位置正好是第一个满足条件的下标。实现查找具体值常用闭区间,寻找下界或插入位置则开区间更自然。

  • 闭区间退出时L=R+1,开区间退出时L=R,这是关键区别
  • 开区间适合求第一个满足条件的位置,闭区间适合查精确值
  • 每次更新必须维持不变量,否则会死循环或漏解

两种区间的收缩规则

左闭右闭中,初始L=0,R=n-1,区间[L,R]内每个下标都可能是答案。每次取mid,若arr[mid]不满足条件,则必须让区间收缩到不包括mid的一侧,于是L=mid+1或R=mid-1。因为只要还有未检查的元素就应继续,循环条件写成while(L<=R),退出时L=R+1,代表区间彻底被排除。

左闭右开中,L指向第一个可能的答案,R指向边界外的哨兵,区间[L,R)始终保持arr[L-1]<target且arr[R]>=target(若存在)。当arr[mid]>=target时,mid可能是答案,收缩右侧为R=mid;否则L=mid+1排除mid。循环条件是L<R,退出时L==R,正好是第一个不满足/满足的分界点。

查找插入位置时的开区间优势

给定升序数组[1,3,3,5,7],需要确定目标值2应该插入的下标,即找到第一个>=2的位置。约束是数组有序且可能重复,要求返回下标。若用左闭右开,直接L=0,R=n,循环结束后返回L即可,无需额外变量,代码天然处理元素全部小于目标时返回n。

左闭右闭同样可以直接返回left作为插入位置,但需要理解当数组全小于target时left会等于数组长度,使用前需验证;开区间写法把终点条件嵌入区间定义,错误率更低。

两种区间的 lowerBound 实现JavaScript
function lowerBoundClosed(arr, target) {
  let left = 0, right = arr.length - 1;
  while (left <= right) {
    const mid = left + ((right - left) >> 1);
    if (arr[mid] >= target) right = mid - 1;
    else left = mid + 1;
  }
  return left;
}

function lowerBoundOpen(arr, target) {
  let left = 0, right = arr.length;
  while (left < right) {
    const mid = left + ((right - left) >> 1);
    if (arr[mid] >= target) right = mid;
    else left = mid + 1;
  }
  return left;
}

const cases = [
  { arr: [1, 3, 3, 5], target: 3 },
  { arr: [1, 3, 3, 5], target: 2 },
  { arr: [1, 3, 3, 5], target: 6 },
  { arr: [], target: 1 },
];

for (const { arr, target } of cases) {
  console.log(`arr=${JSON.stringify(arr)} target=${target} -> closed:${lowerBoundClosed(arr, target)}, open:${lowerBoundOpen(arr, target)}`);
}
查看输出与解释
arr=[1,3,3,5] target=3 -> closed:1, open:1
arr=[1,3,3,5] target=2 -> closed:1, open:1
arr=[1,3,3,5] target=6 -> closed:4, open:4
arr=[] target=1 -> closed:0, open:0

两种写法都对[1,3,3,5]返回第一个不小于目标值的下标:目标3返回1(第一个3),目标2返回1,目标6返回数组长度4;空数组返回0。闭区间通过right=mid-1收缩,开区间通过right=mid保留候选。时间复杂度均为O(log n),空间O(1)。该代码直接运行在Node.js或浏览器控制台。

边界容易失效的坑

左闭右开若误写成while(L<=R),且更新仍用R=mid,当L=R时循环继续,mid等于边界,R=mid不改变,导致无限循环。同样,闭区间如果用L=mid代替mid+1,在mid不等于目标时区间不缩小,也会死循环。判断的关键是检查每次迭代区间长度是否严格递减。

另一个失效场景是目标数组为空。闭区间写法需保证R=n-1,空数组时R=-1,循环不执行,此时若返回L=0是正确插入位置,但若直接访问arr[mid]会越界。开区间初始R=0,同样不执行循环,返回0。处理时留意先判断数组长度或让R从n开始,避免对哨兵取值。

回答前,多想一步

容易答错的地方

认为开区间只是一种风格
其实两者维护的语义完全不同,开区间天然对应二分查找的谓词版,闭区间适合精确查找。混用会导致区间收缩不对,比如在开区间里用R=mid-1会跳过答案。依据是循环不变量必须与更新操作一致。
以为闭区间返回`left`总是正确
闭区间写法和开区间写法最后的left值相同,都指向第一个不小于目标的位置;但数组全小于target时left会越出数组长度,此时用arr[left]会越界。若要找某个确切值,必须额外判断该位置是否存在。
试着用自己的话回答

面试官还会怎么问?

左闭右开写`right = mid`时,为什么不会死循环?

因为当mid<right时区间长度至少为1,若right=left+1则mid=left,此时若arr[left]>=target则right=mid即right=left,循环结束;否则left=mid+1使得区间缩小。每次迭代mid取自下中位数,且收缩方向明确排除一个元素。

查找具体值而不是下界时,两种写法哪个更直接?

查找精确相等用闭区间方便,遇到arr[mid]==target直接返回mid。开区间写法需要先求下界再比较arr[pos]是否等于target,多一步检查,但边界仍一致。工程上求存在性多用闭区间,求范围用开区间。

二分答案时通常用开区间还是闭区间?

二分答案往往找最小可行解或最大可行解,此时用开区间配合单调谓词最自然,如[L,R)表示可行域分界,最终返回R。闭区间需要维护答案变量,两者等价但开区间更少出错。

从一道题,走向一组知识

把知识连起来

搜索与排序

二分查找如何找到目标值的第一个出现位置?

同属「搜索与排序」专题,接着看 二分查找 第一个出现位置 首次出现 在具体场景中的处理方式。

搜索与排序

二分查找失败时如何返回目标值应插入的位置?

同属「搜索与排序」专题,接着看 二分查找 插入位置 在具体场景中的处理方式。

参考资料

  • Binary search¶

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 两种区间的收缩规则
  3. 查找插入位置时的开区间优势
  4. 边界容易失效的坑
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑