704. 二分查找

➡️题目链接

参考链接:https://www.programmercarl.com/0704.二分查找.html#_704-二分查找

第一种解法(暴力解法):

使用暴力解法,遍历数组。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        for i in range(len(nums)):
            if nums[i] == target:
                return i
        else:
            return -1
执行用时:40 ms, 在所有 Python3 提交中击败了76.04%的用户
内存消耗:16 MB, 在所有 Python3 提交中击败了50.88%的用户
通过测试用例:47 / 47
 
第二种解法(二分查找):
这道题目的前提是数组为有序数组,同时题目强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件。当看到题目满足上述条件时,就可以思考是不是可以使用二分法。
二分法查找涉及很多的边界条件,逻辑比较简单,但是边界条件容易混乱。到底是 while(left < right) 还是while(left <= right),到底是right=middle还是right = middle-1呢。
我们在写二分法时经常写乱,通常是对区间的定义没有搞清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写二分法,区间的定义一般有两种,左闭右闭即[left, right],或者左闭右开即[left, right]。
下边我们用两种区间定义分别讲解两种不同的二分写法。
class Solution: # 左闭右闭
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1  # 定义target在左闭右闭的区间里,[left, right]
        while left <= right:
            middle = left + (right - left) // 2
            
            if nums[middle] > target:
                right = middle - 1  # target在左区间,所以[left, middle - 1]
            elif nums[middle] < target:
                left = middle + 1  # target在右区间,所以[middle + 1, right]
            else:
                return middle  # 数组中找到目标值,直接返回下标
        return -1  # 未找到目标值
class Solution: # 左闭右开
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)  # 定义target在左闭右开的区间里,即:[left, right)

        while left < right:  # 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            middle = left + (right - left) // 2

            if nums[middle] > target:
                right = middle  # target 在左区间,在[left, middle)中
            elif nums[middle] < target:
                left = middle + 1  # target 在右区间,在[middle + 1, right)中
            else:
                return middle  # 数组中找到目标值,直接返回下标
        return -1  # 未找到目标值

 

二分法第一种写法
第一种写法,我们定义target是在一个左闭右闭的区间里,也就是[left, right](这个很重要)。
区间的定义这就决定了二分法的代码应该怎么写,因为定义target在[left, right]区间,所以有如下两点:
1. while (left <= right)要使用<=,因为left==right是有意义的,所以用<=
2. if (nums[middle] > target) right要赋值为middle-1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是middle-1
例如在数组:1,2,3,4,7,9,10中查找元素2,如图所示:
 

如果定义target是在一个左闭右开的区间里,也就是[left, right),那么二分法的边界处理方式则截然不同。

有如下两点:

1. while (left < right),这里使用<,因为left == right在区间[left, right)是没有意义的

2. if (nums[middle] > target) right更新为middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

在数组:1,2,3,4,7,9,10中查找元素2,如图所示:(注意和方法一的区别)

 复杂度:

二分查找大大缩减了找的次数,举个例子,我们每次都猜中间的数字,那么那样每次就能至少排除一般的数字,最多只需要log2(n),则时间复杂度是O(logn),空间复杂度是O(1),因为使用了一个整数型变量mid来记录中间值。

总结

二分法是非常重要的基础算法,重要的是对区间定义的理解,在循环中要始终坚持根据查找区间的定义来做边界处理。区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。

还要注意一个小细节,python中的//和/,在python中,/表示浮点整除法,返回浮点结果,也就是结果为浮点数;而在python中表示整数除法,返回大于结果的一个最大整数,也就是除法结果向下取整。

print("6 // 4 = " + str(6 // 4)) #结果:1
print("6 / 4 =" + str(6 / 4)) #结果:1.5

 

 

posted @ 2023-03-16 05:34  我是球啊  阅读(12)  评论(0编辑  收藏  举报