旋转数组里的二分查找:如何进阶应对不确定性?
一、前言:旋转排序数组 VS 普通二分查找
我们都知道 二分查找 在 有序数组 中效率非常高,时间复杂度是 O(log n)。但是,当数组被旋转(比如 [4,5,6,7,0,1,2]
)后,经典的二分查找就不那么好用了,因为数组的 单调性 受到了破坏。
在 搜索旋转排序数组 II(也就是带有重复元素的情况,如 [2,2,2,3,4,2,2]
),传统二分查找遇到了更多挑战:
- 无法简单判断哪一侧是有序区间(因为有可能
nums[mid] == nums[left] == nums[right]
)。 - 可能需要线性扫描,降低算法效率。
那么,如何优化我们的二分查找,让它在 旋转+重复 环境下仍能高效运作呢?咱们来一步步拆解。
二、算法思路
在普通 二分查找 中,我们根据 nums[mid]
的值与 target
的对比,决定往 左侧 或 右侧 继续查找。
而在 搜索旋转排序数组 II 里,我们要处理 额外的不确定性:
- 如果 nums[mid] > nums[left],说明 左侧是有序区间,可以按正常二分逻辑处理。
- 如果 nums[mid] < nums[left],说明 右侧是有序区间。
- 如果 nums[mid] == nums[left] == nums[right],那就麻烦了——我们无法确定有序区间在哪个方向,必须 逐步缩小搜索范围。
代码实现
def search_rotated(nums, target):
"""
在旋转排序数组(含重复元素)中查找目标值
:param nums: 旋转排序数组
:param target: 目标值
:return: 是否找到目标值(True/False)
"""
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2 # 计算中点
# 如果找到目标值,直接返回 True
if nums[mid] == target:
return True
# 处理 nums[left] == nums[mid] == nums[right],无法确定有序区间
if nums[left] == nums[mid] == nums[right]:
left += 1
right -= 1
continue
# 判断左区间是否是有序的
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 False # 未找到目标值
代码解释
- 基础二分查找逻辑:我们依旧按
mid
值和target
的大小比较,决定前进方向。 - 特殊情况处理(nums[left] == nums[mid] == nums[right]):
- 如果边界值都相同,我们就无法确定哪一侧是有序区间,只能线性缩小范围。
- 按有序区间进行搜索:
- 如果 左侧有序,那么
target
是否在nums[left]
到nums[mid]
之间,决定是否向左搜索。 - 如果 右侧有序,那么
target
是否在nums[mid]
到nums[right]
之间,决定是否向右搜索。
- 如果 左侧有序,那么
三、复杂度分析
在 没有重复元素 的旋转数组中,我们的二分查找仍能保持 O(log n) 的时间复杂度。
但在 存在大量重复元素 的情况下,由于 nums[left] == nums[mid] == nums[right]
可能会让我们无法直接缩小搜索范围,这种情况下最坏情况可能退化到 O(n)(类似于线性查找)。
四、应用场景
这种二分查找方法适用于 搜索旋转数组中的特定元素,比如:
- 搜索某个被排序但旋转过的 ID 数据;
- 判断某个数值是否在某个旋转后的区间(例如时间序列数据)。
五、进阶优化:减少线性缩小范围
如果我们发现 nums[left] == nums[mid] == nums[right]
但 nums[mid]
仍然不等于 target
,我们可以尝试 一次性跳过所有相同的元素:
while left <= right and nums[left] == nums[mid] == nums[right]:
left += 1
right -= 1
这样可以 减少不必要的循环,优化处理重复元素的情况。
六、结语
搜索旋转排序数组 II 是经典的二分查找进阶问题,它让我们必须学会:
- 在存在重复元素的情况下进行有序区间判断;
- 如何处理极端情况下的线性扫描;
- 如何优化搜索路径,降低不必要的计算。
二分查找并不总是绝对完美,有时候它需要结合实际数据情况进行优化。