1. 题目解析与核心思路
这道力扣中等难度题目要求我们在一个非递减排序的整数数组中,找到给定目标值的起始和结束位置。如果目标值不存在于数组中,需要返回[-1, -1]。题目要求算法时间复杂度必须控制在O(log n)级别,这意味着我们需要使用二分查找的变种来实现。
1.1 问题本质分析
这道题的核心在于处理有序数组中重复元素的边界定位。普通二分查找在找到目标值后会立即返回,但这里需要继续搜索直到确定元素的边界。举个例子:
- 输入数组 [5,7,7,8,8,10],目标值8
- 正确输出应该是[3,4],因为8第一次出现在索引3,最后一次出现在索引4
1.2 算法选择依据
选择二分查找的变种主要基于以下考虑:
- 有序数组的特性天然适合二分查找
- O(log n)的时间复杂度要求排除了线性扫描的可能性
- 需要分别处理左边界和右边界两种情况
- 常规二分查找无法直接解决重复元素的边界问题
2. 二分查找变种实现细节
2.1 左边界查找算法
查找左边界的二分查找需要做以下调整:
python复制def find_left(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
return left
关键点说明:
- 当nums[mid] == target时,仍然执行right = mid - 1,继续向左搜索
- 循环结束时,left指向的就是第一个等于target的位置
- 需要检查left是否越界以及nums[left]是否确实等于target
2.2 右边界查找算法
右边界的查找逻辑稍有不同:
python复制def find_right(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
return right
实现要点:
- 当nums[mid] == target时,执行left = mid + 1,继续向右搜索
- 循环结束时,right指向最后一个等于target的位置
- 同样需要检查right是否越界以及值是否匹配
3. 完整解决方案实现
3.1 边界条件处理
完整的解决方案需要考虑以下边界情况:
- 空数组输入
- 目标值不存在于数组中
- 目标值比所有元素都小或都大
- 数组中只有一个目标值元素
- 整个数组都是目标值
3.2 Python实现代码
python复制def searchRange(nums, target):
def find_left():
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
return left
def find_right():
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
return right
left_idx = find_left()
right_idx = find_right()
if left_idx <= right_idx and right_idx < len(nums) and nums[left_idx] == target and nums[right_idx] == target:
return [left_idx, right_idx]
return [-1, -1]
4. 算法复杂度与优化
4.1 时间复杂度分析
- 两次二分查找各自是O(log n)
- 最终的时间复杂度仍然是O(log n)
- 比线性扫描的O(n)有显著优势,特别是对于大数组
4.2 空间复杂度考虑
- 只使用了常数级别的额外空间
- 空间复杂度是O(1)
- 没有使用递归,避免了栈空间开销
4.3 可能的优化方向
- 可以在找到左边界后,直接从左边界开始搜索右边界
- 对于某些特殊情况可以提前终止(如目标值不在数组范围内)
- 可以尝试将两个二分查找合并为一个函数
5. 常见错误与调试技巧
5.1 典型错误案例
- 无限循环:边界条件处理不当导致
- 返回错误索引:未验证最终找到的位置是否确实等于target
- 处理空数组时崩溃:未检查数组长度
- 左右边界混淆:把left和right的查找逻辑写反
5.2 调试建议
- 使用小测试用例手动模拟算法执行过程
- 打印中间变量值观察二分查找的过程
- 特别注意循环不变量的维护
- 测试边界情况:空数组、单个元素、全相同元素等
5.3 测试用例设计
好的测试用例应该包括:
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,2], 2, [0,3]), # 全相同
([1,3,5,7], 9, [-1,-1]), # 大于所有
([1,3,5,7], 0, [-1,-1]) # 小于所有
]
6. 实际应用场景
这种边界查找算法在实际开发中有广泛用途:
- 日志系统中查找特定时间范围内的事件
- 数据库查询中定位某个值的范围
- 统计分析中确定某个区间的数据点
- 版本控制系统中查找特定版本的变更范围
理解这种二分查找的变种对于处理有序数据非常重要,它展示了如何通过调整二分查找的条件来处理更复杂的需求。