二分查找(Binary Search)的核心思想是:折半查找 。
前提条件:数组必须是有序的(或者具有某种单调性)。
它的基本步骤是:
- 找到区间的中间点
mid。 - 如果
mid的值等于目标值,直接返回。 - 如果
mid的值大于目标值,说明目标在左半部分,收缩右边界。 - 如果
mid的值小于目标值,说明目标在右半部分,收缩左边界。
1. 35. 搜索插入位置 (简单)
题目大意:
给定一个排序数组和一个目标值,如果找到目标值则返回索引;如果不存在,返回它将会被按顺序插入的位置。
零基础思路:
这就是最标准的二分查找。如果最后没找到,left 指针指向的位置正好就是它该插入的地方。
代码实现:
python
def searchInsert(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return left # 没找到时,left就是插入位置
2. 74. 搜索二维矩阵 (中等)
题目大意:
矩阵每一行从左到右升序,每一行的第一个数比上一行最后一个数大。给你一个 target,判断在不在矩阵里。
零基础思路:
这个矩阵其实可以看作是一个拉平了的一维长数组。
- 假设矩阵是
m行n列,总共有m * n个元素。 - 一维索引
i转换回二维坐标:行 =i // n,列 =i % n。 - 然后直接套用标准二分查找。
代码实现:
python
def searchMatrix(matrix, target):
m, n = len(matrix), len(matrix[0])
left, right = 0, m * n - 1
while left <= right:
mid = (left + right) // 2
# 将一维索引转回二维
row, col = mid // n, mid % n
num = matrix[row][col]
if num == target:
return True
elif num < target:
left = mid + 1
else:
right = mid - 1
return False
3. 34. 在排序数组中查找元素的第一个和最后一个位置 (中等)
题目大意:
给定一个升序数组,找出一个给定目标值的开始位置和结束位置。
零基础思路:
普通的二分查找到一个值就会停止。这里我们需要找到"边界":
- 写一个函数找左边界 :当
nums[mid] == target时,不急着返回,而是让right = mid - 1继续往左找。 - 写一个函数找右边界 :当
nums[mid] == target时,让left = mid + 1继续往右找。
代码实现:
python
def searchRange(nums, target):
def findBound(isFirst):
left, right = 0, len(nums) - 1
bound = -1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
bound = mid
if isFirst: right = mid - 1 # 找左边,锁死右边界
else: left = mid + 1 # 找右边,锁死左边界
elif nums[mid] < target: left = mid + 1
else: right = mid - 1
return bound
return [findBound(True), findBound(False)]
4. 33. 搜索旋转排序数组 (中等)
题目大意:
一个原本升序的数组在某个点旋转了(例如 [0,1,2,4,5,6,7] 变成 [4,5,6,7,0,1,2])。找 target。
零基础思路:
旋转后的数组,如果从中点切开,一定有一半是完全有序的,另一半可能是有序也可能是包含旋转点的。
- 如果
nums[left] <= nums[mid],左半边是有序的。- 检查
target是否在左半边的范围内,在就去左边,不在就去右边。
- 检查
- 否则,右半边是有序的。
- 检查
target是否在右半边的范围内。
- 检查
代码实现:
python
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 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
5. 153. 寻找旋转排序数组中的最小值 (中等)
题目大意:
找出旋转排序数组中的最小元素。
零基础思路:
最小值其实就是旋转点(也就是那个突然变小的地方)。
- 贪心观察: 比较
nums[mid]和nums[right]。 - 如果
nums[mid] > nums[right]:说明最小值一定在mid的右边(因为右边更小,打破了升序规律)。 - 如果
nums[mid] < nums[right]:说明从mid到right是升序的,最小值可能就是mid或者在mid左边。
代码实现:
python
def findMin(nums):
left, right = 0, len(nums) - 1
while left < right: # 注意这里是 <,不是 <=
mid = (left + right) // 2
if nums[mid] > nums[right]:
# 最小值在右半部分
left = mid + 1
else:
# 最小值在左半部分(包含mid)
right = mid
return nums[left]
6. 4. 寻找两个正序数组的中位数 (困难)
题目大意:
给你两个有序数组,求合并后的中位数。要求时间复杂度为 O(log(m+n))O(\log(m+n))O(log(m+n))。
零基础思路:
直接合并再找中位数是 O(m+n)O(m+n)O(m+n),不符合要求。
二分法的精髓在于:在两个数组中分别找一个切割点,使得:
- 左边的总个数等于右边的总个数。
- 左边的最大值 ≤\le≤ 右边的最小值。
由于数组是有序的,我们在短的数组上进行二分查找切口位置 i,长的数组切口位置 j 就会自动确定。
代码实现(简化思路版):
python
def findMedianSortedArrays(nums1, nums2):
if len(nums1) > len(nums2): # 确保 nums1 是短的
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
left, right = 0, m
half_len = (m + n + 1) // 2
while left <= right:
i = (left + right) // 2 # nums1 的切口
j = half_len - i # nums2 的切口
if i < m and nums1[i] < nums2[j-1]:
left = i + 1 # i 太小,右移
elif i > 0 and nums1[i-1] > nums2[j]:
right = i - 1 # i 太大,左移
else:
# 找到合适的切口了!
# 确定左侧最大值
if i == 0: max_of_left = nums2[j-1]
elif j == 0: max_of_left = nums1[i-1]
else: max_of_left = max(nums1[i-1], nums2[j-1])
if (m + n) % 2 == 1: # 总数为奇数
return max_of_left
# 确定右侧最小值
if i == m: min_of_right = nums2[j]
elif j == n: min_of_right = nums1[i]
else: min_of_right = min(nums1[i], nums2[j])
return (max_of_left + min_of_right) / 2.0
总结:二分查找的精髓
- 确定区间: 到底是
while left <= right还是while left < right?- 如果你在循环内能直接找到答案(如
nums[mid] == target),用<=。 - 如果你是在不断逼近一个边界(如找最小值),用
<。
- 如果你在循环内能直接找到答案(如
- 判断逻辑: 核心在于如何排除掉那"一半"不可能的区间。
- 注意溢出: 在某些语言中(如 Java/C++),
mid = (left + right) // 2可能会溢出,通常写成mid = left + (right - left) // 2(Python不需要担心这个)。