对分查找的最多次数_二分查找你不知道的那些事儿

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

因为 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)
在排序数组中查找元素的第一个和最后一个位置​links.jianshu.com

我写的一份实现

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.com

X 的平方根

力扣​leetcode-cn.com

搜索旋转数组

力扣​leetcode-cn.com 力扣​leetcode-cn.com

以上题目是面试中常考题,多练。

最后的最后,周末愉快!日常叫卖,点赞收藏关注。。。

abc12c0af59c2d4e9e260d3936ea730d.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值