二分查找变种
二分查找(Binary Search)是一种高效的搜索算法,通常用于在有序数组中查找特定元素。然而,在实际应用中,我们可能会遇到一些变种问题,这些问题需要对标准的二分查找进行一些调整。本文将介绍几种常见的二分查找变种,并通过代码示例和实际案例帮助你理解它们的应用。
1. 标准二分查找回顾
在深入变种之前,我们先回顾一下标准的二分查找。标准二分查找的基本思想是通过不断将搜索范围缩小一半来查找目标值。以下是一个标准的二分查找实现:
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
输入: arr = [1, 3, 5, 7, 9]
, target = 5
输出: 2
2. 二分查找的常见变种
2.1 查找第一个等于目标值的元素
在某些情况下,数组中可能存在多个相同的目标值,我们需要找到第一个出现的目标值。这时,我们需要对标准二分查找进行一些调整:
def find_first(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] < target:
left = mid + 1
elif arr[mid] > target:
right = mid - 1
else:
if mid == 0 or arr[mid - 1] != target:
return mid
else:
right = mid - 1
return -1
输入: arr = [1, 3, 3, 3, 5]
, target = 3
输出: 1
在这个变种中,当 arr[mid] == target
时,我们还需要检查 arr[mid - 1]
是否也等于 target
。如果等于,说明第一个目标值还在左边,我们需要继续向左搜索。
2.2 查找最后一个等于目标值的元素
类似地,我们也可以找到最后一个等于目标值的元素:
def find_last(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] < target:
left = mid + 1
elif arr[mid] > target:
right = mid - 1
else:
if mid == len(arr) - 1 or arr[mid + 1] != target:
return mid
else:
left = mid + 1
return -1
输入: arr = [1, 3, 3, 3, 5]
, target = 3
输出: 3
在这个变种中,当 arr[mid] == target
时,我们需要检查 arr[mid + 1]
是否也等于 target
。如果等于,说明最后一个目标值还在右边,我们需要继续向右搜索。
2.3 查找第一个大于等于目标值的元素
有时候,我们需要找到第一个大于或等于目标值的元素,即使目标值本身不在数组中:
def find_first_greater_or_equal(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] >= target:
if mid == 0 or arr[mid - 1] < target:
return mid
else:
right = mid - 1
else:
left = mid + 1
return -1
输入: arr = [1, 3, 5, 7, 9]
, target = 6
输出: 3
在这个变种中,我们不仅要找到大于或等于目标值的元素,还要确保它是第一个满足条件的元素。
2.4 查找最后一个小于等于目标值的元素
类似地,我们也可以找到最后一个小于或等于目标值的元素:
def find_last_less_or_equal(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] <= target:
if mid == len(arr) - 1 or arr[mid + 1] > target:
return mid
else:
left = mid + 1
else:
right = mid - 1
return -1
输入: arr = [1, 3, 5, 7, 9]
, target = 6
输出: 2
在这个变种中,我们不仅要找到小于或等于目标值的元素,还要确保它是最后一个满足条件的元素。
3. 实际应用场景
3.1 查找插入位置
假设我们有一个有序数组,我们需要找到目标值应该插入的位置,以保持数组的有序性。这可以通过查找第一个大于等于目标值的元素来实现:
def find_insert_position(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] >= target:
if mid == 0 or arr[mid - 1] < target:
return mid
else:
right = mid - 1
else:
left = mid + 1
return len(arr)
输入: arr = [1, 3, 5, 7, 9]
, target = 6
输出: 3
3.2 查找旋转排序数组中的最小值
在一个旋转排序数组中,我们需要找到最小的元素。这个问题可以通过二分查找的变种来解决:
def find_min_in_rotated_sorted_array(arr):
left, right = 0, len(arr) - 1
while left < right:
mid = left + (right - left) // 2
if arr[mid] > arr[right]:
left = mid + 1
else:
right = mid
return arr[left]
输入: arr = [4, 5, 6, 7, 0, 1, 2]
输出: 0
在这个问题中,我们通过比较 arr[mid]
和 arr[right]
来决定搜索的方向。如果 arr[mid] > arr[right]
,说明最小值在右半部分,否则在左半部分。
4. 总结
二分查找的变种在实际应用中非常有用,尤其是在处理有序数组时。通过调整标准的二分查找算法,我们可以解决更多复杂的问题。掌握这些变种不仅有助于提高编程能力,还能帮助你在面试中脱颖而出。
5. 附加资源与练习
- 练习 1: 实现一个函数,查找数组中第一个小于目标值的元素。
- 练习 2: 在一个包含重复元素的有序数组中,查找目标值的范围(即第一个和最后一个出现的位置)。
- 练习 3: 在一个旋转排序数组中,查找目标值的位置。
如果你想进一步深入学习二分查找及其变种,可以参考《算法导论》或 LeetCode 上的相关题目。