数组篇算法一:二分查找详解
零、问题描述
给定一个 n个元素有序的(升序)整型数组 nums
和一个目标值 target
,编写一个函数搜索 nums
中的 target
。若目标值存在返回下标,否则返回 -1
。
示例:
输入:nums = [-1,0,3,5,9,12], target = 9
输出:4
一、算法适用条件
- 有序性:数组必须按升序或降序排列(通常假设升序)。
- 唯一性(非必须):若数组有重复元素,需明确查找目标(如第一个/最后一个匹配项)。
二、算法步骤
1. 定义搜索区间
- 初始区间:一般为整个数组
- 左边界
left = 0
- 右边界
right
可取len(nums)-1
(闭区间)或len(nums)
(左闭右开)
- 左边界
2. 循环缩小范围
- 计算中间位置:
mid = left + (right - left) // 2
(避免整数溢出) - 比较
nums[mid]
与target
:nums[mid] == target
→ 找到目标,返回索引nums[mid] < target
→ 目标在右半区间,更新left
nums[mid] > target
→ 目标在左半区间,更新right
3. 终止条件
- 找到目标值,直接返回
- 若循环结束未找到,返回
-1
三、两种实现方式
方式1:闭区间写法 [left, right]
核心要点:
right
初始为len(nums)-1
- 循环条件:
while left <= right
- 边界更新:
right = mid - 1
def binary_search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
方式2:左闭右开写法 [left, right)
核心要点:
right
初始为len(nums)
- 循环条件:
while left < right
- 边界更新:
right = mid
def binary_search(nums, target):
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid
return -1
总结对比:
特性 | 闭区间写法 | 左闭右开写法 |
---|---|---|
初始右边界 | len(nums)-1 | len(nums) |
循环条件 | left <= right | left < right |
右边界更新 | right = mid - 1 | right = mid |
四、变体问题
1. 查找第一个等于 target
的位置
核心逻辑:当 nums[mid] >= target
时,继续向左半区间搜索。
def find_first(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
return left if left < len(nums) and nums[left] == target else -1
2. 查找最后一个等于 target
的位置
核心逻辑:当 nums[mid] <= target
时,继续向右半区间搜索。
def find_last(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 if right >= 0 and nums[right] == target else -1
3. 查找第一个大于等于 target
的位置
应用场景:插入位置问题(如 LeetCode 35)
def lower_bound(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
return left # 若 target 大于所有元素,返回 len(nums)
五、经典例题
- LeetCode 704. 二分查找
- 直接应用标准二分查找模板
- LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置
- 结合变体问题中的第一个/最后一个位置查找
- LeetCode 35. 搜索插入位置
- 等价于查找第一个大于等于
target
的位置
- 等价于查找第一个大于等于
六、注意事项
- 避免死循环:确保区间更新能缩小范围(如
left = mid + 1
而非left = mid
)。 - 处理越界情况:返回插入位置时需检查
left
是否超出数组范围。 - 验证有序性:若数组未排序,需先排序(时间复杂度升至 O(n log n))。
- 重复元素处理:明确需求是找第一个、最后一个还是任意位置。