探秘数组搜索:玩转二分查找的经典应用!
大家好,我是忍者算法。今天我要和大家分享一道非常经典的二分查找题目 - LeetCode 34「在排序数组中查找元素的第一个和最后一个位置」。这道题看似简单,实则暗藏玄机,是理解二分查找边界处理的绝佳材料。
📚 从生活场景说起
想象你在整理一叠按时间顺序排好的照片,其中有多张是同一天拍的。如果要找出某一天最早和最晚拍的那张照片,你会怎么做?高效的方法是先用二分找到这一天的任意一张照片,然后再分别向左右寻找边界。这正是我们今天要解决的问题的生活映射!
💡 问题解析
题目要求:
给定一个按升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值,返回 [-1, -1]。
示例:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4] // 8在位置3和位置4出现
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1] // 6不存在于数组中
🤔 思路发展历程
让我们看看解决这个问题时,思维是如何层层递进的:
1. 朴素思路
最直观的方法是遍历一遍数组,记录第一次和最后一次出现的位置。但这种方法的时间复杂度是O(n),没有利用数组已排序的特性。
2. 二分查找思路
既然数组已排序,我们可以用二分查找将时间复杂度优化到O(log n)。关键在于设计两个二分查找:一个找左边界,一个找右边界。
🚀 优雅的解决方案
class Solution {
public int[] searchRange(int[] nums, int target) {
// 特判:空数组的情况
if (nums == null || nums.length == 0) {
return new int[]{-1, -1};
}
// 分别查找左右边界
int leftBorder = findBorder(nums, target, true);
int rightBorder = findBorder(nums, target, false);
return new int[]{leftBorder, rightBorder};
}
// 查找边界的统一函数,isLeft为true时查找左边界,false时查找右边界
private int findBorder(int[] nums, int target, boolean isLeft) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 找到目标值时的处理
if (isLeft) {
// 查找左边界时,收缩右边界
if (mid == 0 || nums[mid - 1] != target) {
return mid;
}
right = mid - 1;
} else {
// 查找右边界时,收缩左边界
if (mid == nums.length - 1 || nums[mid + 1] != target) {
return mid;
}
left = mid + 1;
}
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 没找到目标值
}
}
📝 代码详解
让我们深入理解这个优雅的解决方案:
1. 整体架构
我们设计了一个统一的边界查找函数,通过布尔参数控制是查找左边界还是右边界。这种设计既减少了代码重复,又让逻辑更加清晰。
2. 边界查找的精妙之处
当找到目标值时,我们并不立即返回,而是:
- 查找左边界时,我们要确认前一个数不是目标值
- 查找右边界时,我们要确认后一个数不是目标值
这样就能精确定位边界位置。
3. 条件判断的艺术
代码中的边界检查(mid == 0 或 mid == nums.length - 1)确保了我们不会发生数组越界。这些细节体现了代码的健壮性。
🎯 易错点剖析
-
返回值处理
- 必须先判断数组为空的情况
- 当目标值不存在时,要返回[-1, -1]
-
边界条件
- 别忘了检查数组首尾的特殊情况
- 当找到目标值时,不要急于返回
-
循环终止条件
- while循环的条件是 left <= right
- 这确保了不会漏掉单个元素的情况
💡 举一反三
这道题的思路可以延伸到很多场景:
-
查找最后一个小于目标值的位置
- 只需稍微修改边界判断条件
-
查找第一个大于目标值的位置
- 类似的二分思路,不同的判断条件
-
统计目标值的出现次数
- 可以用右边界减去左边界再加1
🌟 面试技巧
-
展示思维过程
- 先说明暴力解法,再优化到二分
- 体现你的算法思维能力
-
代码优化意识
- 展示代码复用和模块化的能力
- 注意代码的可读性和维护性
-
考虑周全
- 主动提及边界情况的处理
- 展示你考虑问题的全面性
作者:忍者算法
公众号:忍者算法
🎁 回复【刷题清单】获取LeetCode高频面试题合集
🧑💻 回复【代码】获取多语言完整题解
💡 回复【加群】加入算法交流群,一起进步
#算法面试 #LeetCode #二分查找 #数组