详解二分搜索的细节

本文详细探讨了二分搜索算法的实现细节,包括初始化左右指针、循环条件以及更新指针的策略。针对寻找目标值、左边界和右边界三种不同情况,给出了具体的代码实现。强调了循环条件与右边界初始化对搜索区间的影响,以及在不同场景下如何调整搜索策略。
摘要由CSDN通过智能技术生成

前言

Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky…
二分搜索的想法很直白,但细节很致命

本文假定所有数组均是升序排列的,且可能有重复元素。

二分搜索适用于寻找target、寻找左边界(计算小于target的元素个数)、寻找右边界(计算大于target的元素个数)。

二分搜索模板:

int binarySearch(int[] nums, int target) {
    int left = 0, right = ...;

    while(...) {
        int mid = (right + left) / 2;
        if (nums[mid] == target) {
            ...
        } else if (nums[mid] < target) {
            left = ...
        } else if (nums[mid] > target) {
            right = ...
        }
    }
    return ...;
}

下面我们来讨论模板里的三个组成部分:

  1. 初始化i,j
  2. 何时结束循环
  3. 更新i,j值

初始化i,j

关于左右指针的初始化,有人喜欢j = N,有人喜欢j = N-1所以,我觉得首先要明确一下这两种写法下初始mid的物理位置。

假设数组长度为N,下面分奇偶讨论:

N/2(N-1)/2
奇数正中间一个正中间一个
偶数中间偏右的中间偏左的

建议自己随便取几个数试试。

循环条件

初始化右指针有两种,j = Nj = N-1 ;循环的条件亦有两种写法,i<ji<=j,一共有四种组合:

  1. 若初始化j = N,则搜索区间为 [0,N),此时循环条件应为i<j,停止循环时i == j[i,j) 为空
  2. 若初始化j = N,则搜索区间为 [0,N),此时循环条件若为i<=j,则可能越界
  3. 若初始化j = N-1,则搜索区间为 [0,N-1],此时循环条件可以是i<=j,停止循环时i+1 == j
  4. 若初始化j = N-1,则搜索区间为 [0,N-1],此时循环条件可以是i==j,停止循环时i == j,需手动检查一下停止时指向的数字

可以看出,是左闭右开还是左闭右闭只与右边界有关。极端情况下,j一直不动,只有i在向j逼近,需考虑j的位置;正常情况下,ij都会更新,直到搜索区间为空。

要注意的是,有时候需在循环内部加上退出条件,否则会陷入死循环,至于循环中如何更新ij的位置则视需求而定,具体的应用请阅读下面三栏代码

寻找一个数

def bi(nums,target):
    left = 0
    right = len(nums)-1
    while (left <= right):
        mid = (left + right) // 2 
        if (nums[mid] == target):
            return mid
        elif (nums[mid] < target):
            left = mid + 1
        elif (nums[mid] > target):
            right = mid - 1
    return -1

nums = [2,3,3,6,6,8,10]
target1 = 7
target2 = 10
print('Done! index = ',bi(nums,target1))
print('Done! index = ',bi(nums,target2))
#Output:Done! index =  -1
#Output:Done! index =  6

等价于

def bi(nums,target):
    left = 0
    right = len(nums)
    while (left < right):
        mid = (left + right) // 2 
        if (nums[mid] == target):
            return mid
        elif (nums[mid] < target):
            left = mid + 1
        elif (nums[mid] > target):
            right = mid - 1
    return -1

nums = [2,3,3,6,6,8,10]
target1 = 7
target2 = 10
print('Done! index = ',bi(nums,target1))
print('Done! index = ',bi(nums,target2))
#Output:Done! index =  -1
#Output:Done! index =  6

等价于

def bi(nums,target):
    left = 0
    right = len(nums)-1
    while (left < right):
        mid = (left + right) // 2 
        if (nums[mid] == target):
            return mid
        elif (nums[mid] < target):
            left = mid + 1
        elif (nums[mid] > target):
            right = mid - 1
    if nums[left] == target:
        return left
    else:
        return -1

nums = [2,3,3,6,6,8,10]
target1 = 7
target2 = 10
print('Done! index = ',bi(nums,target1))
print('Done! index = ',bi(nums,target2))

我们需要 nums[mid]== target,所以,当nums[mid] != target 时直接将 mid 抛弃,缩放左右边界为mid±1

寻找左边界

def bi(nums,target):
    left = 0
    right = len(nums)
    #区间为[left,right)
    while (left < right):
        mid = (left + right) // 2 
        if (nums[mid] == target):
        # 更新right 下一次在[left,mid)中搜索
        # 若[left,mid)中的数均target了 left也会++ 最后也会和mid重合
        # 指向第一个等于target的数
            right = mid 
        elif (nums[mid] < target):
        # 当前nums[mid]<target 直接舍弃掉
        # 下一次在[mid+1,right)中搜索
            left = mid + 1
        elif (nums[mid] > target):
        # 当前mid>target 更新right 下一次在[left,mid)中搜索
        # 即使[left,mid)中没有target了 left也会++ 最后也会和mid重合
        # 但此时指向的是第一个大于target的数
            right = mid
    return left

nums = [2,3,3,6,6,8,10]
target1 = 3
target2 = 7
print('Done! index = ',bi(nums,target1))
print('Done! index = ',bi(nums,target2))
#Output:Done! index =  1
#Output:Done! index =  5

寻找右边界

def bi(nums,target):
    left = 0
    right = len(nums)
    #区间为[left,right)
    while (left < right):
        mid = (left + right) // 2 
        if (nums[mid] == target):
        # 更新right 下一次在[mid+1,right)中搜索
        # ij重合时指向最后一个等于target的数右边的数
            left = mid + 1 
        elif (nums[mid] < target):
        # 当前nums[mid]<target 下一次在[mid+1,right)中搜索
            left = mid + 1
        elif (nums[mid] > target):
        # 当前nums[mid]>target 更新right 下一次在[left,mid)中搜索
        # 若[left,mid)中所有数均小于target left也会++ 最后也会和mid重合
        # 指向的是第一个不小于target的数
            right = mid
    return left - 1

nums = [2,3,3,6,6,8,10]
target1 = 3
target2 = 7
print('Done! index = ',bi(nums,target1))
print('Done! index = ',bi(nums,target2))
#Output:Done! index =  2
#Output:Done! index =  4
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值