1. 查找区间问题概述
查找区间(Search for a Range)是力扣(LeetCode)平台上编号为34的经典算法题,属于二分查找的变种问题。题目要求在一个已排序的整数数组中,找出给定目标值的起始和结束位置。如果目标值不存在于数组中,则返回[-1, -1]。
这个问题看似简单,却考察了以下几个核心能力:
- 对二分查找算法的深入理解
- 边界条件的处理能力
- 代码实现的精确性
- 时间复杂度分析能力
在实际工程中,类似的需求非常常见。比如:
- 日志系统中查找特定时间范围内的记录
- 数据库查询中检索某个字段值的范围
- 统计分析中确定某个数值的分布区间
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题分析与解法思路
2.1 问题描述与示例
给定一个按照升序排列的整数数组nums,和一个目标值target。找出给定目标值在数组中的开始位置和结束位置。
示例1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
2.2 暴力解法分析
最直观的解法是线性扫描:
- 初始化结果数组为[-1,-1]
- 从左到右遍历数组,记录第一个等于target的索引
- 继续遍历,记录最后一个等于target的索引
- 返回结果
这种方法的时间复杂度是O(n),空间复杂度是O(1)。虽然简单,但在大规模数据下效率不高。
2.3 二分查找优化思路
由于数组是有序的,我们可以利用二分查找将时间复杂度优化到O(log n)。核心思路是:
- 使用二分查找找到target的第一个出现位置
- 使用二分查找找到target的最后一个出现位置
- 组合两个结果返回
关键在于如何修改标准的二分查找来分别找到起始和结束位置。
3. 详细解法实现
3.1 寻找起始位置
寻找起始位置的二分查找实现要点:
- 当nums[mid] >= target时,继续向左搜索
- 当nums[mid] < target时,向右搜索
- 最终检查left是否在数组范围内且nums[left]等于target
python复制def findFirst(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] >= target:
right = mid - 1
else:
left = mid + 1
if left < len(nums) and nums[left] == target:
return left
return -1
3.2 寻找结束位置
寻找结束位置的二分查找实现要点:
- 当nums[mid] <= target时,继续向右搜索
- 当nums[mid] > target时,向左搜索
- 最终检查right是否在数组范围内且nums[right]等于target
python复制def findLast(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] <= target:
left = mid + 1
else:
right = mid - 1
if right >= 0 and nums[right] == target:
return right
return -1
3.3 完整解法代码
将两个辅助函数组合起来:
python复制def searchRange(nums, target):
first = findFirst(nums, target)
if first == -1:
return [-1, -1]
last = findLast(nums, target)
return [first, last]
4. 边界条件与测试用例
4.1 常见边界情况
- 空数组:nums = [], target = 0 → [-1,-1]
- 单个元素匹配:nums = [1], target = 1 → [0,0]
- 单个元素不匹配:nums = [1], target = 2 → [-1,-1]
- 所有元素相同:nums = [2,2,2], target = 2 → [0,2]
- 目标值在开头:nums = [1,2,3], target = 1 → [0,0]
- 目标值在结尾:nums = [1,2,3], target = 3 → [2,2]
- 目标值不存在但位于范围内:nums = [1,3,5], target = 2 → [-1,-1]
- 目标值小于最小值:nums = [1,3,5], target = 0 → [-1,-1]
- 目标值大于最大值:nums = [1,3,5], target = 6 → [-1,-1]
4.2 测试代码示例
python复制def test_searchRange():
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], 2, [0,2]),
([1,3,5], 2, [-1,-1])
]
for nums, target, expected in test_cases:
result = searchRange(nums, target)
assert result == expected, f"Failed: nums={nums}, target={target}, got {result}, expected {expected}"
print("All test cases passed!")
5. 算法复杂度分析
5.1 时间复杂度
- 两次二分查找,每次都是O(log n)
- 总时间复杂度:O(log n) + O(log n) = O(log n)
5.2 空间复杂度
- 只使用了常数级别的额外空间
- 空间复杂度:O(1)
6. 常见错误与调试技巧
6.1 常见实现错误
-
无限循环:边界条件处理不当导致
- 解决方法:确保每次迭代left或right至少移动1位
-
漏掉边界元素:检查条件不完整
- 解决方法:在返回前验证找到的索引确实等于target
-
混淆起始和结束位置的查找逻辑
- 解决方法:明确区分两种情况的判断条件
6.2 调试技巧
- 打印中间变量:
python复制print(f"left={left}, right={right}, mid={mid}, nums[mid]={nums[mid]}")
-
使用小规模测试用例手动模拟:
- 在纸上画出数组和指针移动过程
-
检查极端情况:
- 空数组
- 单元素数组
- 全相同元素数组
7. 算法优化与变种
7.1 单次二分查找优化
可以尝试在一次二分查找中同时确定起始和结束位置,但实现会更复杂:
python复制def searchRangeOptimized(nums, target):
def binarySearch(nums, target, findFirst):
left, right = 0, len(nums) - 1
result = -1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
result = mid
if findFirst:
right = mid - 1
else:
left = mid + 1
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return result
first = binarySearch(nums, target, True)
if first == -1:
return [-1, -1]
last = binarySearch(nums, target, False)
return [first, last]
7.2 相关变种问题
- 统计目标值出现次数:结束位置-起始位置+1
- 查找插入位置:类似起始位置的查找
- 在旋转排序数组中查找目标值:需要先找到旋转点
- 在二维矩阵中查找目标值:将二维索引转换为一维
8. 实际应用场景
-
数据库索引范围查询:
- B+树索引的区间查找实现原理类似
- 用于高效检索某个范围内的记录
-
日志分析系统:
- 查找特定时间段的日志记录
- 统计某个错误码出现的频率
-
数据分析:
- 确定某个数值在数据集中的分布区间
- 统计某个范围内的数据点数量
-
游戏开发:
- 查找玩家分数在排行榜中的位置范围
- 匹配相似技能等级的玩家
9. 扩展学习资源
-
力扣相关题目:
-
- 搜索插入位置
-
- 二分查找
-
- 寻找旋转排序数组中的最小值
-
- 寻找峰值
-
-
算法书籍推荐:
- 《算法导论》中的二分查找章节
- 《编程珠玑》中的二分查找应用案例
- 《算法(第4版)》中的搜索算法部分
-
在线学习资源:
- LeetCode二分查找专题
- 算法可视化网站(如VisualGo)中的二分查找演示
- 各大高校的算法公开课(如MIT 6.006)
在实际面试中,查找区间问题经常被用作考察候选人对二分查找的理解深度。我建议在掌握基础解法后,尝试自己推导出优化解法,并思考如何将其应用到实际工程问题中。记住,算法学习的最终目的是解决实际问题,而不仅仅是应对面试。
