js中,什么是二分查找?

13 阅读5分钟

在 JavaScript 中,二分查找(Binary Search)是一种高效的在有序数组中查找特定元素的算法。它的基本思想是:每次将查找范围缩小一半,从而快速逼近目标值。

✅ 二分查找的前提条件:

  • 数组必须是已排序的(通常是升序)。
  • 适用于静态数据不频繁变动的数据,因为排序本身有开销。

🔍 算法思路(以升序数组为例):

  1. 设置两个指针:left = 0right = arr.length - 1
  2. left <= right 时循环:
    • 计算中间索引:mid = Math.floor((left + right) / 2)
    • 如果 arr[mid] === target,找到目标,返回 mid
    • 如果 arr[mid] < target,说明目标在右半部分,令 left = mid + 1
    • 如果 arr[mid] > target,说明目标在左半部分,令 right = mid - 1
  3. 如果循环结束还没找到,返回 -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 安全)

三、前端面试答题技巧

  1. 先说思路再写代码

    “因为数组有序,我考虑使用二分查找,时间复杂度 O(log n)……”

  2. 强调边界处理

    “这里用 left <= right 是因为采用闭区间;而找峰值时用 left < right 避免访问越界。”

  3. 对比其他方法(加分项)

    “虽然哈希表查找是 O(1),但需要 O(n) 预处理且不适用于有序场景,二分更优。”

  4. 手写时注意细节

    • Math.floor>> 1(JS 中 >> 对负数有坑,建议用 Math.floor
    • 变量命名清晰(left, right, mid

四、总结

题型核心变化面试频率
标准查找直接返回⭐⭐⭐⭐
插入位置返回 left⭐⭐⭐
查找区间两次二分⭐⭐⭐⭐
寻找峰值比较相邻元素⭐⭐

💬 面试官常问

  • “如果数组有重复元素,二分还能用吗?” → 能,但需调整逻辑(如找边界)
  • “二分查找最坏时间复杂度是多少?” → O(log n),不是 O(n)(那是线性查找)

建议刷题顺序:LeetCode 704 → 35 → 34 → 162 → 875