在 JavaScript 中,二分查找(Binary Search)是一种高效的在有序数组中查找特定元素的算法。它的基本思想是:每次将查找范围缩小一半,从而快速逼近目标值。
✅ 二分查找的前提条件:
- 数组必须是已排序的(通常是升序)。
- 适用于静态数据或不频繁变动的数据,因为排序本身有开销。
🔍 算法思路(以升序数组为例):
- 设置两个指针:
left = 0,right = arr.length - 1。 - 当
left <= right时循环:- 计算中间索引:
mid = Math.floor((left + right) / 2) - 如果
arr[mid] === target,找到目标,返回mid - 如果
arr[mid] < target,说明目标在右半部分,令left = mid + 1 - 如果
arr[mid] > target,说明目标在左半部分,令right = mid - 1
- 计算中间索引:
- 如果循环结束还没找到,返回
-1(表示未找到)
💡 JavaScript 实现示例:
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
// 找到目标,返回索引
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
// 目标在右侧
left = mid + 1;
} else {
// 目标在左侧
right = mid - 1;
}
}
return -1;
}
console.group('迭代版本')
console.log(arr)
console.log('4 ->', binarySearch(arr, 4))
console.log('8 ->', binarySearch(arr, 8))
console.log('11 ->', binarySearch(arr, 11))
const arr2 = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
{ id: 4, name: '赵六' },
{ id: 5, name: '孙七' },
{ id: 6, name: '周八' },
{ id: 7, name: '吴九' },
{ id: 8, name: '郑十' },
{ id: 9, name: '王十一' },
{ id: 10, name: '张十二' },
{ id: 11, name: '周十三' },
]
// arr2 不适合做二分查找(包括递归版本)
// 如果使用 id 适合做二分查找
// 如果是 name 适合使用线性查找
⏱️ 时间复杂度:
- O(log n) — 每次比较都将搜索空间减半。
🧠 空间复杂度:
- O(1)(迭代版本)或 O(log n)(递归版本,因调用栈)
🔄 递归版本(可选):
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function binarySearchRecursive(arr, target, left = 0, right = arr.length - 1) {
if (left > right) {
return -1;
}
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
return binarySearchRecursive(arr, target, mid + 1, right);
} else {
return binarySearchRecursive(arr, target, left, mid - 1);
}
}
console.group('递归版本')
console.log(arr)
console.log('4 ->', binarySearchRecursive(arr, 4))
console.log('8 ->', binarySearchRecursive(arr, 8))
console.log('11 ->', binarySearchRecursive(arr, 11))
console.groupEnd();
⚠️ 注意事项:
- 如果数组未排序,二分查找会出错!
- 对于有重复元素的数组,二分查找可能返回任意一个匹配位置(如需找第一个/最后一个,需稍作修改)。
面试题解
在前端面试中,二分查找(Binary Search) 是一道高频算法题,尤其在考察数据结构与算法基础时经常出现。虽然前端开发更侧重 DOM、组件、框架等,但大厂(如字节、阿里、腾讯、美团等)的笔试/面试中仍会要求手写或分析二分查找及其变种。
一、为什么前端要考二分查找?
- 考察逻辑思维能力和边界处理能力
- 验证是否理解 O(log n) 时间复杂度 的实现
- 二分查找是很多高级算法(如双指针、滑动窗口、单调栈)的基础
- 常见于 LeetCode 面试经典 150 题 中(如 704、35、34、162 等)
二、经典题型 & 题解
✅ 题型 1:标准二分查找(LeetCode 704)
在升序无重复数组中查找 target,存在返回下标,否则返回 -1。
function search(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = left + Math.floor((right - left) / 2); // 防溢出
if (nums[mid] === target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
📌 关键点:
- 循环条件
left <= right(闭区间)mid计算防溢出(虽 JS 不会整数溢出,但体现工程习惯)- 时间复杂度:O(log n),空间:O(1)
✅ 题型 2:搜索插入位置(LeetCode 35)
若 target 存在返回下标;不存在则返回应插入的位置(保持有序)。
function searchInsert(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = left + Math.floor((right - left) / 2);
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left; // 最终 left 就是插入位置
}
💡 核心思想:即使没找到,left 指针最终停在第一个大于 target 的位置
✅ 题型 3:查找目标值的起始和结束位置(LeetCode 34)
数组可能有重复元素,要求返回
[first, last],时间复杂度必须 O(log n)
思路:两次二分 —— 一次找左边界,一次找右边界
function searchRange(nums, target) {
const findBound = (isLeft) => {
let left = 0;
let right = nums.length - 1;
let result = -1;
while (left <= right) {
const mid = left + Math.floor((right - left) / 2);
if (nums[mid] === target) {
result = mid;
if (isLeft) {
right = mid - 1; // 继续向左找
} else {
left = mid + 1; // 继续向右找
}
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
};
const leftIdx = findBound(true);
const rightIdx = findBound(false);
return [leftIdx, rightIdx];
}
⚠️ 易错点:
- 找到 target 后不能直接 return,要继续收缩区间
- 左边界:
right = mid - 1;右边界:left = mid + 1
✅ 题型 4:寻找峰值(LeetCode 162)
峰值:
nums[i] > nums[i-1] && nums[i] > nums[i+1],假设nums[-1] = nums[n] = -∞
function findPeakElement(nums) {
let left = 0;
let right = nums.length - 1;
while (left < right) { // 注意:这里是 <,不是 <=
const mid = left + Math.floor((right - left) / 2);
if (nums[mid] > nums[mid + 1]) {
right = mid; // 峰值在左边(含 mid)
} else {
left = mid + 1; // 峰值在右边
}
}
return left; // left === right
}
🔍 关键洞察:
- 只需比较
nums[mid]和nums[mid + 1]- 因为两端是 -∞,一定存在峰值
- 使用
left < right避免越界(mid + 1安全)
三、前端面试答题技巧
-
先说思路再写代码
“因为数组有序,我考虑使用二分查找,时间复杂度 O(log n)……”
-
强调边界处理
“这里用
left <= right是因为采用闭区间;而找峰值时用left < right避免访问越界。” -
对比其他方法(加分项)
“虽然哈希表查找是 O(1),但需要 O(n) 预处理且不适用于有序场景,二分更优。”
-
手写时注意细节
Math.floor或>> 1(JS 中>>对负数有坑,建议用Math.floor)- 变量命名清晰(
left,right,mid)
四、总结
| 题型 | 核心变化 | 面试频率 |
|---|---|---|
| 标准查找 | 直接返回 | ⭐⭐⭐⭐ |
| 插入位置 | 返回 left | ⭐⭐⭐ |
| 查找区间 | 两次二分 | ⭐⭐⭐⭐ |
| 寻找峰值 | 比较相邻元素 | ⭐⭐ |
💬 面试官常问:
- “如果数组有重复元素,二分还能用吗?” → 能,但需调整逻辑(如找边界)
- “二分查找最坏时间复杂度是多少?” → O(log n),不是 O(n)(那是线性查找)
建议刷题顺序:LeetCode 704 → 35 → 34 → 162 → 875