![449aa065695b98bc99da4506617c3f7c.png](https://img-blog.csdnimg.cn/img_convert/449aa065695b98bc99da4506617c3f7c.png)
今天是周五,即将告别六月的第一周,心情好,于是就没了写文章的兴致,一心只想周末。不知道写啥,那就讲一下二分查找吧。这个也是面试中的重中之重了。
二分查找适用场景
二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。
二分查找时间复杂度
二分查找的效率是非常的惊人的,时间复杂度达到了 O(logN)。
你一定开始纳闷了,为啥是O(logN),下面咱们详细的分析一下。我们假设数组长度为 n ,每次利用二分查找,我们查找的区间都会变为原来的一半,依次类推,得出如下:
数组查找区间变化
N, N/2, N/2^2 … N/2^k
可以看出来,这是一个等比数列。其中 n/2k=1 时,k 的值就是总共缩小的次数。而每一次缩小操作只涉及两个数据的大小比较,所以,经过了 k 次区间缩小操作,时间复杂度就是 O(k)。通过 n/2k=1,我们可以求得 k=log2n,所以时间复杂度就是 O(logN)。
通过时间复杂度我们还不足以去惊叹二分查找的效率,稍微量化一下你就知道它的效率有多么恐怖了
![41c6a096733af0bd5ec384e810c0e85c.png](https://img-blog.csdnimg.cn/img_convert/41c6a096733af0bd5ec384e810c0e85c.png)
因为 logN 是一个非常“恐怖”的数量级,即便 N 非常非常大,对应的 logN 也很小。比如 N 等于 2 的 32 次方,这个数很大了吧?大约是 42 亿。也就是说,如果我们在 42 亿个数据中用二分查找一个数据,最多需要比较 32 次。
咱们接下来看看二分查找的代码怎么写吧?
两种实现
遍历
def bsearch(self, array, target):
low = 0
high = len(array) - 1
while low <= high:
mid = low + ((high-low)>>1)
if array[mid] == target:
return mid
elif array[mid] < target:
low = mid + 1
else:
high = mid - 1
return False
遍历实现容易出错的地方
- 循环退出条件
注意是 low<=high,而不是 low
- mid 的取值
实际上,我们mid = low+((high-low)>>1)写成这样是为了将性能优化到极致,计算机处理位运算比除法是要快的。我们可能会想取中间位置不就是 mid=(low+high)/2 就可以了么?你一定会有这样的疑问,其实这种疑问开始我自己也会存在,经过思考以后发现,这种写法是有问题的。因为如果 low 和 high 比较大的话,两者之和就有可能会溢出。那么咱们可以改写为 mid = low+(high-low)/2 等价于 mid = low+((high-low)>>1)
- low 和 high 的更新
low=mid+1,high=mid-1。注意这里的 +1 和 -1,如果直接写成 low=mid 或者 high=mid,就可能会发生死循环。比如,当 high=3,low=3 时,如果 a[3]不等于 value,就会导致一直循环不退出。
递归
def bsearch(self, array, target):
low = 0
high = len(array) - 1
return self.bsearchInternally(array, target, low, high)
def bsearchInternally(self, array, target, low, high):
if low > high:
return False
mid = low + ((high - low) >> 1)
if array[mid] == target:
return mid
elif array[mid] < target:
return self.bsearchInternally(array, target, mid + 1, high)
else:
return self.bsearchInternally(array, target, low, mid - 1)
看完了你一定会说“光说不练假把式”,我就喜欢你这种实在的人儿呀,下面贴几道经典二分查找算法的题
在排序数组中查找数字出现的次数
力扣leetcode-cn.com排序数组 nums 中的所有数字 target 形成一个窗口,记窗口的 左 / 右边界 索引分别为 left 和 right ,分别对应窗口左边 / 右边的首个元素。
本题要求统计数字 target 的出现次数,可转化为:使用二分法分别找到 左边界 left 和 右边界 right ,易得数字 target 的数量为 right - left −1 。
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
# 时间复杂度 O(n) 空间复杂度 O(1)
# 暴力法
# if not nums:
# return 0
# i = 0
# for num in nums:
# if num == target:
# i += 1
# return i
def helper(tar):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + ((right - left) >> 1)
if nums[mid] <= tar:
left = mid + 1
else:
right = mid - 1
return left
return helper(target) - helper(target - 1)
我写的一份实现
class Solution(object):
def searchRange(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
return [self.bsearch(nums, target, True), self.bsearch(nums, target, False)]
def bsearch(self, nums, target, firstOrLast):
low = 0
high = len(nums) - 1
while low <= high:
mid = low + ((high - low) >> 1)
if nums[mid] < target:
low = mid + 1
elif nums[mid] > target:
high = mid - 1
else:
if firstOrLast:
if mid == 0 or nums[mid - 1] != target:
return mid
else:
high = mid - 1
else:
if mid == len(nums) - 1 or nums[mid + 1] != target:
return mid
else:
low = mid + 1
return -1
查找旋转数组中的最小数字
力扣leetcode-cn.com寻找重复数
力扣leetcode-cn.comX 的平方根
力扣leetcode-cn.com搜索旋转数组
力扣leetcode-cn.com 力扣leetcode-cn.com以上题目是面试中常考题,多练。
最后的最后,周末愉快!日常叫卖,点赞收藏关注。。。
![abc12c0af59c2d4e9e260d3936ea730d.png](https://img-blog.csdnimg.cn/img_convert/abc12c0af59c2d4e9e260d3936ea730d.png)