LeetCode 二分查找算法详解:原理、变体与实战技巧

引言

二分查找是一种高效的查找算法,常用于在有序数组中快速定位特定元素。本文将深入探讨二分查找算法的基本原理、常见变体、应用场景以及在 LeetCode 上的实际应用。

1. 二分查找基本原理

二分查找的核心思想是通过比较目标值与数组中间元素的大小,不断缩小查找范围,直到找到目标值或确定目标值不存在。

基本步骤:

  1. 确定查找范围的左右边界
  2. 计算中间位置
  3. 将中间元素与目标值比较
  4. 根据比较结果缩小查找范围
  5. 重复步骤2-4,直到找到目标值或确定目标值不存在

2. 二分查找的基本实现

1
2
3
4
5
6
7
8
9
10
11
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

3. 二分查找的常见变体

3.1 查找第一个等于目标值的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
def search_first_equal(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
else:
if mid == 0 or nums[mid - 1] < target:
return mid
right = mid - 1
return -1

3.2 查找最后一个等于目标值的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
def search_last_equal(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
else:
if mid == len(nums) - 1 or nums[mid + 1] > target:
return mid
left = mid + 1
return -1

3.3 查找第一个大于等于目标值的元素

1
2
3
4
5
6
7
8
9
10
11
def search_first_greater_equal(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
if mid == 0 or nums[mid - 1] < target:
return mid
right = mid - 1
return -1

3.4 查找最后一个小于等于目标值的元素

1
2
3
4
5
6
7
8
9
10
11
def search_last_less_equal(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] > target:
right = mid - 1
else:
if mid == len(nums) - 1 or nums[mid + 1] > target:
return mid
left = mid + 1
return -1

4. LeetCode 实战应用

这是一个标准的二分查找问题。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def search(self, nums: List[int], target: int) -> int:
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

4.2 LeetCode 35. Search Insert Position

这个问题要求我们在有序数组中找到目标值的索引,如果不存在,则返回它应该被插入的位置。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
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 left

4.3 LeetCode 33. Search in Rotated Sorted Array

这个问题涉及在旋转排序数组中进行二分查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid

if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1

return -1

4.4 LeetCode 74. Search a 2D Matrix

这个问题要求在二维矩阵中进行二分查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if not matrix or not matrix[0]:
return False

m, n = len(matrix), len(matrix[0])
left, right = 0, m * n - 1

while left <= right:
mid = left + (right - left) // 2
value = matrix[mid // n][mid % n]

if value == target:
return True
elif value < target:
left = mid + 1
else:
right = mid - 1

return False

5. 二分查找的应用场景

  1. 有序数组查找: 在有序数组中快速查找特定元素。
  2. 查找边界: 寻找满足特定条件的元素的边界。
  3. 优化问题: 在某些优化问题中,可以使用二分查找来快速逼近最优解。
  4. 数据库索引: 在数据库索引结构中应用二分查找原理。

6. 二分查找的技巧与注意事项

  1. 边界条件处理: 注意循环的终止条件,通常使用 left <= right
  2. 中点计算: 使用 mid = left + (right - left) // 2 可以避免整数溢出。
  3. 死循环处理: 确保在每次循环中,搜索范围都在缩小。
  4. 溢出问题: 对于大数问题,注意处理可能的整数溢出。
  5. 精度问题: 对于浮点数二分,需要考虑精度问题。

7. 二分查找的优化思路

  1. 预处理: 对于某些问题,可以先对数据进行预处理,如排序,以便应用二分查找。
  2. 二分答案: 对于一些判定问题,可以对答案进行二分查找。
  3. 多次二分: 某些复杂问题可能需要多次应用二分查找。
  4. 结合其他算法: 二分查找可以与其他算法(如双指针、滑动窗口等)结合使用。

8. 二分查找的局限性

尽管二分查找非常高效,但也存在一些局限性:

  1. 要求有序: 二分查找要求数组必须是有序的,这可能需要额外的排序步骤。
  2. 不适用于动态数据: 对于频繁插入和删除的数据结构,二分查找可能不太适用。
  3. 内存访问模式: 在某些硬件架构上,二分查找的内存访问模式可能不如顺序访问高效。
  4. 小数据集: 对于小数据集,简单的线性搜索可能更快。

结语

二分查找是一种强大而高效的算法,在处理有序数据时特别有用。通过本文的学习,我们深入了解了二分查找的基本原理、常见变体、LeetCode 实战应用以及各种技巧和注意事项。在实际应用中,要根据具体问题的特点,选择合适的二分查找策略,并结合其他算法技巧来优化解决方案。通过不断练习和总结,我们可以在算法解题中越来越熟练地运用二分查找,解决各种复杂的查找和优化问题。