文章目录

  • 二分查找
  • 一、思路
  • 二、相关题目解读
  • 二分查找(leetcode 704.)
  • 搜索插入位置(leetcode 35.)
  • 有效的完全平方数(leetcode 367.)
  • 在排序数组中查找元素的第一个和最后一个位置(leetcode 34.)
  • 总结



二分查找

数组原理介绍
数组是非常基础的数据结构,数组是存放在连续内存空间上的相同类型数据的集合。数组可以方便的通过下标索引的方式获取到下标下对应的数据。
需要注意的两点是,数组下标都是从0开始的;数组内存空间的地址是连续的。正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。数组的元素是不能删的,只能覆盖。(这句话可能大家不太好理解,指的是某个位置的数组元素是删除不了的,删除也是移动后面的元素接替上覆盖的意思,这个位置还是有元素的.)


一、思路

二分查找前提是数组为有序数组,同时还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。二分法时间复杂度:O(logn)。
使用二分查找需要非常注意的一点是,二分查找的边界条件问题,看起来逻辑简单,但是一般不容易写好。主要是因为我们对区间的定义没有想清楚。在二分查找的过程中,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

二、相关题目解读

下面我用这两种区间的定义分别讲解两种不同的二分写法。让大家更能体会二分查找的原理。

二分查找(leetcode 704.)

代码如下(示例):

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left=0
        right=len(nums)-1
        while left<=right:#建立左闭右闭的区间
            mid=(left+right)//2
            if nums[mid]<target:
                left=mid+1
            elif nums[mid]>target:
                right=mid-1
            else:
                return mid
        return -1
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left=0
        right=len(nums)#注意这里的变化
        while left<right:#建立左闭右开的区间
            mid=(left+right)//2
            if nums[mid]<target:
                left=mid+1
            elif nums[mid]>target:
                right=mid#注意这里的变化
            else:
                return mid
        return -1

为了防止大家看了代码还是云里雾里,这里稍微再解释一下,对于建立左闭右闭区间,那么[left,right],比如[1,1]是可取的,所以while循环里left<=right,right取值len(nums)-1;但是当建立左闭右开区间,那么[left,right),比如[1,1)是不可取的,所以while循环里left<right,right取值len(nums)。


搜索插入位置(leetcode 35.)

代码如下(示例):

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        # 要求找到第一个大于等于目标值的位置,然后将目标值插入此元素位置
        left=0
        right=len(nums)-1
        while left<=right:#建立左闭右闭区间
            mid=(left+right)//2
            if nums[mid]<target:
                left=mid+1
            elif nums[mid]>target:
                right=mid-1
            else:
                return mid
        return right+1
 时间复杂度O(logn),空间复杂度O(1)

有效的完全平方数(leetcode 367.)

class Solution:
    def isPerfectSquare(self, num: int) -> bool:
        if num==1:
            return True
        left=1
        right=num-1
        while left<=right:
            mid=(left+right)//2
            if mid*mid>num:
                right=mid-1
            elif mid*mid<num:
                left=mid+1
            else:
                return True
        return False
 时间复杂度O(logn),空间复杂度O(1)

二分查找需要把数据进行折半,这题其实就是想看对于一个数num,能否找到一个在[1,num]之间的数x,使x2=num,如果mid2大于num,说明要向左找,否则向右找。等于时就找到了,搜索完了还没有找到,那就是不存在。


在排序数组中查找元素的第一个和最后一个位置(leetcode 34.)

# 1、首先,在 nums 数组中二分查找 target;
# 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
# 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def binarySearch(nums:List[int], target:int) -> int:
            left, right = 0, len(nums)-1
            while left<=right: # 左闭右闭区间
                middle = (left + right) // 2
                if nums[middle] > target:
                    right = middle - 1
                elif nums[middle] < target: 
                    left = middle + 1
                else:
                    return middle
            return -1
        index = binarySearch(nums, target)
        if index == -1:return [-1, -1] # nums 中不存在 target,直接返回 {-1, -1}
        # nums 中存在 targe,则左右滑动指针,来找到符合题意的区间
        left, right = index, index
        # 向左滑动,找左边界
        while left -1 >=0 and nums[left - 1] == target: left -=1
        # 向右滑动,找右边界
        while right+1 < len(nums) and nums[right + 1] == target: right +=1
        return [left, right]

总结

以上就是对二分法的全部详细介绍。这篇文章根据两种常见的区间定义,给出了两种二分法的写法,每一个边界为什么这么处理,都根据区间的定义做了详细介绍。大部分人主要就是对区间的定义没有理解清楚,在循环中没有始终坚持根据查找区间的定义来做边界处理。希望大家多做练习,锻炼自己手撕二分的能力!