二分查找的“精准打击”——LeetCode 34题全解析

156 阅读3分钟

二分查找的“精准打击”——LeetCode 34题全解析

“你以为二分查找只能查一个?其实它还能查一串!”
—— 一位被面试官问懵的程序员

前言

在算法的江湖里,二分查找一直是“快、准、狠”的代名词。可你知道吗?它不仅能帮你找到目标元素,还能帮你精准锁定一串目标的起点和终点。今天,我们就用 LeetCode 34 题“在排序数组中查找元素的第一个和最后一个位置”来聊聊二分查找的“精准打击”!


一、题目简介

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值,返回 [-1, -1]
要求时间复杂度为 O(log n)。

是不是觉得这题和普通的二分查找有点不一样?没错,这次我们要查的不是一个点,而是一段区间!


二、二分查找的“分身术”:找头找尾

2.1 常规二分查找回顾

普通二分查找只关心“有没有”,找到就返回下标,没找到就返回 -1。
但这题要找“第一个”和“最后一个”出现的位置,怎么办?
别慌,二分查找有“分身术”!

2.2 技术核心:两次二分查找

  • 第一次:找第一个等于 target 的位置(左边界)
  • 第二次:找最后一个等于 target 的位置(右边界)
2.2.1 找第一个等于 target 的位置

每次遇到 nums[mid] === target,别急着返回,先记录下来,然后继续往左边找,看看还有没有更靠前的“同伙”。

2.2.2 找最后一个等于 target 的位置

同理,遇到 nums[mid] === target,先记下,再往右边找,看看有没有更靠后的“兄弟”。


三、代码实现

来,直接上代码,绝不藏私:

var searchRange = function(nums, target) {
    // 查找第一个等于target的位置
    function findFirst(nums, target) {
        let left = 0, right = nums.length - 1, res = -1;
        while (left <= right) {
            let mid = left + ((right - left) >> 1);
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                if (nums[mid] === target) res = mid;
                right = mid - 1;
            }
        }
        return res;
    }
    // 查找最后一个等于target的位置
    function findLast(nums, target) {
        let left = 0, right = nums.length - 1, res = -1;
        while (left <= right) {
            let mid = left + ((right - left) >> 1);
            if (nums[mid] > target) {
                right = mid - 1;
            } else {
                if (nums[mid] === target) res = mid;
                left = mid + 1;
            }
        }
        return res;
    }
    return [findFirst(nums, target), findLast(nums, target)];
};

四、技术细节大揭秘

4.1 为什么不用一次二分查找?

因为数组里可能有一串连续的 target,普通二分查找只会停在“某一个”target上,无法保证是最左或最右。

4.2 为什么遇到 target 还要继续找?

  • 找左边界时,遇到 target 还要往左缩区间,直到不能再缩。
  • 找右边界时,遇到 target 还要往右扩区间,直到不能再扩。

这就像找队伍里第一个和最后一个穿红衣服的人,不能只看到一个就满足,得把队头队尾都摸清楚。

4.3 时间复杂度分析

每次二分查找都是 O(log n),两次加起来还是 O(log n)。
面试官要的就是这个“对数级别”的速度!


五、常见坑点

  1. 数组为空怎么办?
    直接返回 [-1, -1],别犹豫。

  2. target 不存在怎么办?
    两次查找都返回 -1,组合成 [-1, -1]

  3. 边界处理要小心
    leftright 的更新和循环条件要写对,不然容易死循环或者漏掉答案。


六、幽默总结

二分查找就像“精准制导导弹”,不仅能一击命中,还能锁定整个目标区间。
学会了这招,面试官再问“区间查找”你都能自信一笑:“小意思,二分查找分分钟搞定!”


七、代码小剧场

let nums = [5,7,7,8,8,10], target = 8;
console.log(searchRange(nums, target)); // 输出: [3,4]

“8”在队伍里排第3到第4位,二分查找一眼就看穿了它的“伪装”!