前端进阶之旅前端进阶之旅
  • 基础篇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 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库二分查找 第一个出现位置 首次出现
算法算法搜索与排序

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

采用二分区间收缩时,命中目标不立即返回,而是记录当前位置并继续向左搜索,循环结束后该记录即为目标值的首次出现下标。

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

收缩方向决定左边界

  1. 三向分支小于、等于、大于分别调整左或右界
  2. 命中向左收缩等于时记下标后继续搜左半段
  3. 终止条件左指针越过右指针时循环结束

候选下标只可能不断左移,不会漏掉更早的相同值。

核心回答

先记住这个答案

用两个指针维护搜索区间,当中间值等于目标时,先把结果记为当前下标,再令右指针移到该位置左侧继续寻找更靠前的相等元素;循环结束后,记录的下标就是第一个出现位置。若从未命中,则返回 -1。时间 O(log n),空间 O(1)。

  • 命中目标后继续向左收缩区间
  • 用候选下标记录当前最左命中
  • 未命中返回 -1,不混淆插入位

让相等分支也收缩是关键

标准二分查找在 arr[mid] == target 时立即返回 mid,但这只能保证找到某个目标,不能保证是第一个。因为左侧可能还有相等元素。解决办法是:记录当前命中位置 ans = mid,然后强制把右边界移到 mid - 1,继续在左半段寻找。即使左边没有更多目标,收缩过程也会在区间为空后自然结束,此时 ans 就是最左边的命中点。

另一种思路是先求第一个大于等于目标的下标 lower_bound,再检查该下标对应元素是否等于目标。两种方式等价,但直接实现相等收缩更直观,也便于理解为什么方向选择至关重要。每次迭代丢弃一半不可能含首个目标的区间,因此时间复杂度稳定为 O(log n)。

firstOccurrence 实现与边界测试JavaScript
function firstOccurrence(arr, target) {
  let left = 0, right = arr.length - 1, ans = -1;
  while (left <= right) {
    const mid = left + Math.floor((right - left) / 2);
    if (arr[mid] === target) {
      ans = mid;
      right = mid - 1;
    } else if (arr[mid] < target) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }
  return ans;
}

console.log(firstOccurrence([1,2,2,2,3], 2));
console.log(firstOccurrence([5,7,7,8,8,10], 8));
console.log(firstOccurrence([5,7,7,8,8,10], 6));
console.log(firstOccurrence([], 1));
查看输出与解释
1
3
-1
-1

每次把区间折半,只保留可能含有更靠左相同元素的部分。数组为空或目标不存在时 ans 保持 -1。运行时间 O(log n),空间 O(1)。

成绩单查找首个满分的位置

假设考试系统里有一份升序排列的成绩数组,例如 [60, 60, 72, 72, 72, 90],需要找出第一个 72 分所在的下标,以便确定第一个达到该分数的学生。若直接用普通二分,可能命中中间的某个 72 就返回,从而漏掉更早出现的位置。系统要求返回最早出现的位置,因此可用本题算法。

采用上述算法,若第一次命中 index = 3,记录该位置并把右边界移到 mid - 1(即收缩到左半段),继续寻找更靠前的等于 72 的元素;随后可能命中 index = 2,再次记录并移动右边界;当左区间 [0,1] 的元素都小于 72 时,循环结束,返回 2。该过程能稳定得到最左侧的相等元素下标。

空数组与全不命中时的返回语义

若数组为空,初始 left=0, right=-1,循环条件 left <= right 不成立,函数直接返回 -1,表示没有目标。当目标小于所有元素,如 [2,3,5] 找 1,循环中所有中间值都大于 1,right 持续左移直到 -1,同样返回 -1。

当目标大于所有元素,如 [2,3,5] 找 7,left 最终越过数组末尾 right,但 ans 从未被更新,依然返回 -1。这里需要明确:本函数语义是“目标是否存在并返回其首现”,并非返回“应插入位置”。因此它与 lower_bound 的语义不同,后者在找不到时返回可插入的下标。若面试者混淆二者,会导致结果不一致,必须在出口处澄清。

回答前,多想一步

容易答错的地方

命中后马上返回
错误。arr[mid] == target 时直接返回只能得到任意一个命中点,左侧可能还有相同值。应记录 mid 后令 right = mid - 1 继续搜索,直到区间为空,ans 才必然是第一个位置。
把返回值当成 lower_bound
部分实现会返回第一个大于等于目标的下标,判断 arr[pos]==target 后再决定。但本题要求“找第一个出现”,若目标不存在应返回 -1,而不是 pos 本身。两种语义容易混用,需要按题目要求调整出口。
试着用自己的话回答

面试官还会怎么问?

如何通过该函数得到最后一个出现位置?

对称地改动相等分支:命中时记录并令 left = mid + 1 向右搜索,最终得到最右命中点。也可先求最后一个 <= target 的位置,再验证等于目标,从而复用现有代码。

为什么 `mid` 取左中位且 `left <= right` 也能正确工作?

循环结束时 left > right,所有可能的相等元素区间都已检查。取左中位在偶数长度下偏左,不影响收缩方向,只是避免死循环。关键在相等分支必须改变边界,使区间严格缩小。

若数组未排序,这方法还适用吗?

不适用。二分依赖有序性来排除区间,未排序时无法确定目标可能在左还是右。必须先排序,但排序后原索引会丢失,若需保留原序则另寻他法,如线性扫描。

从一道题,走向一组知识

把知识连起来

搜索与排序

二分查找如何定位目标值的最后一个出现位置?

两者对称,对比首尾两次搜索的收缩方向与记录方式,可加深对二分变体的理解。

搜索与排序

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

本算法若改为返回第一个大于等于位置的下标,就得到插入位置,理解二者联系能灵活切换题目语义。

参考资料

  • Binary search¶

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

本题目录
  1. 先记住这个答案
  2. 让相等分支也收缩是关键
  3. 成绩单查找首个满分的位置
  4. 空数组与全不命中时的返回语义
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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