二分查找的“精准打击”——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],别犹豫。 -
target 不存在怎么办?
两次查找都返回 -1,组合成[-1, -1]。 -
边界处理要小心
left、right的更新和循环条件要写对,不然容易死循环或者漏掉答案。
六、幽默总结
二分查找就像“精准制导导弹”,不仅能一击命中,还能锁定整个目标区间。
学会了这招,面试官再问“区间查找”你都能自信一笑:“小意思,二分查找分分钟搞定!”
七、代码小剧场
let nums = [5,7,7,8,8,10], target = 8;
console.log(searchRange(nums, target)); // 输出: [3,4]
“8”在队伍里排第3到第4位,二分查找一眼就看穿了它的“伪装”!