跳到主要内容

二分查找

简介

二分查找也常被称为二分法或者折半查找,每次查找时通过将待查找区间分成两部分并只取 一部分继续查找,将查找的复杂度大大减少。

二分查找的题目变化比较多,但是总结起来就是三种变式,我们可以参考 Leetbook-binary-search

模板

经典二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target。如果目标值存在返回下标,否则返回 -1。

经典二分查找适用于

  • 非重复的数组的元素查找

  • 查找条件不需要与两边的元素进行比较,找的是特定值

代码

# 经典二分查找
def binarySearch(arr, l, r, target):
while l <= r:
pivot = int(l + (r - l)/2)
if arr[pivot] == target:
return pivot
if arr[pivot] < target:
l = pivot + 1
else:
r = pivot - 1
return -1

# 递归实现
def binarySearch(arr, l, r, target):
while l <= r:
pivot = int(l + (r - l) / 2)
if arr[pivot] == target:
return pivot
if arr[pivot] < target:
return binarySearch(arr, pivot + 1, r, target)
else:
return binarySearch(arr, l, pivot + 1, target)

左闭右开

二分查找的高级模式

  • 查找的元素不是一个特定的值,而是一种最近似的值。比如 第一个错误的版本第一个最接近X的值
  • 查找条件需要访问元素的直接右邻居

模板

查找元素靠右的五个条件

  • left = 0, right = len(nums) // 左闭右开,为了保证最后一个元素能取到,所以为 len(nums)
  • left < right // 这里是没有等号的
  • mid = (left + right) >> 1 // mid 靠左才能不死循环
  • left = mid + 1 //元素靠右,所以移动是右移
  • right = mid // 不动,这里一定要和上面的 len(nums) 及 mid 的取值方式对应起来,不然死循环。
# 查找元素靠右
def binarySearch(nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if len(nums) == 0:
return -1

left, right = 0, len(nums) # right 是右开的
while left < right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid

# Post-processing:
# End Condition: left == right
if left != len(nums) and nums[left] == target:
return left
return -1

查找元素靠左,返回的时候直接输出 left - 1 即可

注意点:mid 的位置如何确定

下面举一个例子,我们要从如下数组中,找到第一个1,也就是寻找靠右的元素。

我们现在left指向序号$2$,right指向序号$3$

0 0 [0 1] 1 1

当我们找到1的时候,我们不拒绝它,因为1就是我们需要寻找的数字,我们无法确定是不是第一个,所以right = mid ,而当我们找到0的时候,很确定不是我们要找的,所以left = mid + 1

mid = (left + right) // 2 + 1 即中位数靠右的时候,上面的例子就会陷入死循环出不来,所以一定要靠左。即 mid = (left + right) // 2

# 查找第一个 1,元素靠右
def search_first_one(nums):
left, right = 0, len(nums)
while left < right:
mid = (left + right) // 2
if nums[mid] == 0:
left = mid + 1
else:
right = mid
return right

# 查找最后一个 0 ,元素靠左
def search_last_zero(nums):
left, right = 0, len(nums)
while left < right:
mid = (left + right) // 2 + 1
if nums[mid] == 0:
left = mid
else:
right = mid - 1
return left