文章目录
引言
二分查找的问题定义为:
给定一个由数字组成的有序数组
nums
,并给你一个数字target
。问nums 中是否存在target。如果存在,则返回其在nums 中的索引。如果不存在,则返回-1
。
常见的变体有:
- 如果存在多个满足条件的元素,返回最左边满足条件的索引。
- 如果存在多个满足条件的元素,返回最右边满足条件的索引。
- 数组不是整体有序的。比如先升序再降序,或者先降序再升序。
- 将一维数组变成二维数组。
我们要注意:二分查找的前提是数组必须是有序的。如果给定数组是无序的,在运用二分查找前需要先排序。
查找一个数
算法描述:
先从数组的中间元素开始,
如果中间元素正好是要查找的元素,则搜索过程结束;
如果目标元素大于中间元素,则在数组大于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。
如果目标元素小于中间元素,则在数组小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。
如果在某一步骤数组为空,则代表找不到。
思维框架:
首先定义搜索区间为[left, right],注意是左右都闭合,之后会用到这个点。
由于定义的搜索区间为[left, right],因此当left <= right 的时候,搜索区间都不为空,此时我们都需要继续搜索。也就是说终止搜索条件应该为left <= right
。
- 循环体内,我们不断计算mid,并将nums[mid]与目标值比对。
- 如果nums[mid]等于目标值,则提前返回mid(只需要找到一个满足条件的即可)
如果nums[mid]小于目标值,说明目标值在mid右侧,这个时候搜索区间可缩小为[mid + 1, right]
如果nums[mid]大于目标值,说明目标值在mid左侧,这个时候搜索区间可缩小为[left,mid - 1]
- 循环结束都没有找到,则说明找不到,返回-1表示未找到
代码实现:
def binary_serach(lis,key):
low = 0
high = len(lis) - 1
# 终止搜索条件是搜索区间为空,定义的搜索区间为闭合区间[low,high]
# 如果low < high,则搜索区间我[low,high),此时不会判断low=high的那个元素
while low <= high:
mid = int(low +(high - low) / 2)
if lis[mid] == key:
return mid
elif lis[mid] >= key:
high = mid - 1
elif lis[mid] <= key:
low = mid + 1
# 循环结束条件是搜索空间为空,即数组中不含有目标值
return -1
if __name__ == '__main__':
nums = [1, 3, 4, 5, 6, 8, 12, 14, 16]
target = 8
result = binary_serach(nums, target)
print(result)
寻找最左边的满足条件的值
思维框架:
首先定义搜索区间为[left, right],注意是左右都闭合,之后会用到这个点。
由于定义的搜索区间为[left, right],因此当left <= right 的时候,搜索区间都不为空,此时我们都需要继续搜索。也就是说终止搜索条件应该为left <= right
。
- 循环体内,我们不断计算mid,并将nums[mid]与目标值比对。
- 如果nums[mid]等于目标值,则收缩右边界,我们找到了一个备胎,继续看看左边还有没有了
如果nums[mid]小于目标值,说明目标值在mid右侧,这个时候搜索区间可缩小为[mid + 1, right]
如果nums[mid]大于目标值,说明目标值在mid左侧,这个时候搜索区间可缩小为[left,mid - 1]
- 由于不会提前返回,因此我们需要检查最终的left,看 nums[left]是否等于target。
如果不等于target,或者left出了右边边界了,说明至死都没有找到一个备胎,则返回-1
.否则返回left 即可,备胎转正。
def binary_serach(lis, key):
low, high = 0, len(lis) - 1
while low <= high:
mid = int(low + (high - low) / 2)
# =与>这里可以合并
if lis[mid] == key:
# 收缩右边界
high = mid - 1
if lis[mid] > key:
high = mid - 1
if lis[mid] < key:
low = mid + 1
# 如果不等于key,或者left出了右边边界了
if low >= len(lis) or lis[low] != key:
return -1
return low
if __name__ == '__main__':
nums = [1, 2, 3, 5, 5, 5, 12, 14, 16]
target = 5
result = binary_serach(nums, target)
print(result)
面试题 08.03. 魔术索引
在数组A[0…n-1]中,有所谓的魔术索引,满足条件
A[i] = i
。给定一个有序整数数组,编写一种方法找出魔术索引,若有的话,在数组A中找出一个魔术索引,如果没有,则返回-1。若有多个魔术索引,返回索引值最小的一个。
示例:
输入:nums = [0, 2, 3, 4, 5]
输出:0
说明: 0下标的元素为0
class Solution:
def findMagicIndex(self, nums: List[int]) -> int:
return self.find_magic_index(nums,0,len(nums)-1)
def find_magic_index(self,nums,left,right):
# 未找到符合要求的,返回-1
if left > right:
return -1
# 中间元素
mid = int(left + (right - left) / 2)
# 看是否能够找到第一个魔术索引,先往左侧找
left_ans = self.find_magic_index(nums,left,mid-1)
# 如果左侧存在,那么返回left_ans
if left_ans != -1:
return left_ans
# 如果左侧不存在,先比较nums[mid] == mid
elif nums[mid] == mid:
return mid
# 如果左侧不存在,且nums[mid] != mid,往右侧找
return self.find_magic_index(nums,mid+1,right)
- 时间复杂度:最坏情况下会达到 O(n) 的时间复杂度,其中 n 为数组的长度。
- 空间复杂度:递归函数的空间取决于调用的栈深度,而最坏情况下我们会递归 nn、 层,即栈深度为 O(n),因此空间复杂度最坏情况下为O(n)。
寻找最右边的满足条件的值
思维框架:
首先定义搜索区间为[left, right],注意是左右都闭合,之后会用到这个点。
由于定义的搜索区间为[left, right],因此当left <= right 的时候,搜索区间都不为空,此时我们都需要继续搜索。也就是说终止搜索条件应该为left <= right
。
- 循环体内,我们不断计算mid,并将nums[mid]与目标值比对。
- 如果nums[mid]等于目标值,则收缩左边界,我们找到了一个备胎,继续看看右边还有没有了
如果nums[mid]小于目标值,说明目标值在mid右侧,这个时候搜索区间可缩小为[mid + 1, right]
如果nums[mid]大于目标值,说明目标值在mid左侧,这个时候搜索区间可缩小为[left,mid - 1]
- 由于不会提前返回,因此我们需要检查最终的right,看 nums[right]是否等于target。
如果不等于target,或者right出了左边边界了,说明至死都没有找到一个备胎,则返回-1
.否则返回right即可,备胎转正。
def binary_serach(lis, key):
low, high = 0, len(lis) - 1
while low <= high:
mid = int(low + (high - low) / 2)
# =与<这里可以合并
if lis[mid] == key:
# 收缩左边界
low = mid + 1