1. 问题背景与核心需求
这道题目是LeetCode上经典的二分查找变种问题,编号34。给定一个按照非递减顺序排列的整数数组nums和一个目标值target,我们需要找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值,则返回[-1, -1]。
这个问题的实际意义在于:在有序数据集中快速定位某个元素的出现范围。比如在日志分析中查找某个时间戳范围内的记录,或者在电商系统中查询某个价格区间的商品。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法设计思路解析
2.1 基础二分查找的局限性
标准的二分查找算法只能确定元素是否存在,或者返回任意一个匹配位置。对于包含重复元素的有序数组,我们需要改进算法来找到元素的边界。
python复制def binary_search(nums, target):
left, right = 0, len(nums)-1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
2.2 边界查找的二分变种
我们需要实现两个辅助函数:
- find_first(): 查找第一个等于target的位置
- find_last(): 查找最后一个等于target的位置
关键点在于当nums[mid] == target时如何处理:
- 查找第一个位置时,继续向左搜索(right = mid - 1)
- 查找最后一个位置时,继续向右搜索(left = mid + 1)
3. 完整算法实现
3.1 查找第一个位置的实现
python复制def find_first(nums, target):
left, right = 0, len(nums)-1
first = -1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
first = mid
right = mid - 1 # 继续向左搜索
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return first
3.2 查找最后一个位置的实现
python复制def find_last(nums, target):
left, right = 0, len(nums)-1
last = -1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
last = mid
left = mid + 1 # 继续向右搜索
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return last
3.3 组合解决方案
python复制def searchRange(nums, target):
first = find_first(nums, target)
if first == -1:
return [-1, -1]
last = find_last(nums, target)
return [first, last]
4. 算法复杂度分析
时间复杂度:O(log n)
- 两次二分查找,每次都是O(log n)
- 常数项可以忽略
空间复杂度:O(1)
- 只使用了常数个额外变量
5. 边界条件与测试用例
5.1 典型测试用例
python复制test_cases = [
([5,7,7,8,8,10], 8, [3,4]), # 正常情况
([5,7,7,8,8,10], 6, [-1,-1]), # 不存在目标值
([], 0, [-1,-1]), # 空数组
([1], 1, [0,0]), # 单元素匹配
([2,2], 2, [0,1]) # 全数组匹配
]
5.2 特殊边界情况
- 数组中所有元素都等于target
- target小于数组最小值
- target大于数组最大值
- 数组中只有一个元素且匹配
- 数组中只有一个元素不匹配
6. 算法优化与变种
6.1 单次遍历实现
可以修改标准二分查找,在找到target后向左右扩展搜索边界。但在最坏情况下(整个数组都是target)会退化为O(n)。
python复制def searchRange_single_pass(nums, target):
left, right = 0, len(nums)-1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
# 向左右扩展
l = r = mid
while l >= 0 and nums[l] == target:
l -= 1
while r < len(nums) and nums[r] == target:
r += 1
return [l+1, r-1]
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return [-1, -1]
6.2 使用bisect模块
Python的bisect模块可以简化实现:
python复制import bisect
def searchRange_bisect(nums, target):
left = bisect.bisect_left(nums, target)
if left == len(nums) or nums[left] != target:
return [-1, -1]
right = bisect.bisect_right(nums, target) - 1
return [left, right]
7. 实际应用场景
- 日志系统:查找特定时间范围内的日志条目
- 电商平台:查询某个价格区间的商品
- 学生成绩系统:统计某个分数段的学生人数
- 时间序列分析:定位特定数值范围的数据点
8. 常见错误与调试技巧
8.1 典型错误
-
无限循环:边界条件处理不当导致
- 解决方法:确保每次迭代left或right至少移动1
-
漏掉边界元素:
- 解决方法:仔细检查循环终止条件
-
混淆first和last的逻辑:
- 解决方法:明确区分两种搜索方向
8.2 调试技巧
-
打印中间变量:
python复制print(f"left={left}, right={right}, mid={mid}, nums[mid]={nums[mid]}") -
使用小规模测试用例:
- 手动计算预期结果
- 逐步跟踪算法执行
-
可视化数组和指针位置:
- 画图表示数组和left/right/mid指针
9. 扩展思考
9.1 处理浮点数数组
当数组包含浮点数时,需要注意比较的精度问题:
python复制def float_equal(a, b, epsilon=1e-9):
return abs(a - b) < epsilon
9.2 多维数据查找
对于多维数据,可以结合空间划分数据结构如KD-tree来实现范围查询。
9.3 流数据场景
对于不断有新数据加入的场景,可以考虑使用跳表(Skip List)等动态数据结构。
