二分法终极解读

本文深入探讨了二分查找算法,包括基本的寻找目标元素、寻找大于目标值的位置、寻找大于等于目标值的位置等应用场景。详细解析了左闭右开和左闭右闭两种区间处理方式,并通过实例分析了循环退出条件和区间变换。同时,提到了二分法在LeetCode等题目中的应用,如寻找旋转排序数组中的最小值。
摘要由CSDN通过智能技术生成

 

 

目录

 

二分法是啥

1.最基本的二分法模块---寻找一个数

最基本的二分法框架(左闭右开)

左闭右闭

2.寻找第一个大于目标值的位置

3.寻找第一个大于等于目标值的位置(左边界)

4.其他采用二分法的题目


二分法是啥

最近研究二分法,感觉对里面的各种边界及左右指针的指向产生了深深的怀疑,有时候要加等号,有时候要左闭右开区间,有时候要左开右闭区间。就很头疼,就花时间仔细研究了一下,一点点写左右指针移动的情况,最终略有所懂,分享一下。

说到二分法,就想到了以前中小学的一个问题,有N个鸡蛋,有一个鸡蛋比较重,其他都是一样重,现在只有一个平衡称,怎么最快进行找到这个比较独特的鸡蛋,就是一次分成两堆,然后每次把重的那堆拿出来再进行比较。一直重复,直到找到。只需要logN次查找就可以找到,缩短了原来的N次实验。

二分查找看起来其实很简单,就是每次都分成一半,然后再去找,细节却是魔鬼,要怎么样给mid值变换。它主要就是适用于单调的数组,这个条件不能忘,不是所有的都能适用,一定是得具有单调性。接下来,我就详细介绍二分法的各种场景

1.最基本的二分法模块---寻找一个数

这个就是最基本的,给定你一个数组,然后再给一个目标数target,在这个数组里面找到这个目标值的索引,找不到返回-1。

最基本的二分法框架(左闭右开)

def binarySearch(nums,target):
    '''
 
    :param nums: 目标数组
    :param target: 目标元素
    :return: 返回的目标元素的索引值,找不到的话返回-1
    '''
    left=0
    right=len(nums)
    while(left<right):
        mid=(left+right)>>1
        if (nums[mid]>target):
            right=mid
        elif(num[mid]<target):
            left=mid+1
        else:
            return mid
    return -1

首先就是这个左右指针的取值问题,现在是采用左闭右开的方式,即[left,right)咱就是在这个区间里面去寻找目标值,首先先取一半,mid=(left+right)>>1向下取整,然后如果这个值就是目标值,那就直接返回,找到了。

如果不是那就是得判断大了还是小了,当前值比目标值要大说明再左边,就是把右边界变成mid,那现在的区间就是[left,mid)因为mid已经判断过了,所以不再进行判断,如果是小了,就是在右边,现在区间就是[mid,right),一下子就把要寻找的区域变成了一半,这就是二分法的真谛,不断的去缩小搜寻区间找到最小值。

接着说一下最后退出的条件,因为是左闭右开的,最后一次进入判断都是左右间隔为1,假如为[2,3)这个时候区间就只为2一个值,进去mid=2判断如果成功就返回,如果不成功就是要么右边界减1,要么左边界加1。然后就退出循环,返回-1。以上就是这个基本框架,那么有人要问了我怎么见过有的地方是right=len(nums)-1呢,别急,接下来就是要说这种情况。

左闭右闭

def binarySearch(nums,target):
    '''
 
    :param nums: 目标数组
    :param target: 目标元素
    :return: 返回的目标元素的索引值,找不到的话返回-1
    '''
    left=0
    right=len(nums)-1
    while(left<=right):
        mid=(left+right)>>1
        if (nums[mid]>target):
            right=mid-1
        elif(num[mid]<target):
            left=mid+1
        else:
            return mid
    return -1

其实整体上只有3个变化,一个是变成左闭右闭了,这样就导致了循环退出条件得是left>right才能退出,为啥呢,因为假如现在区间是[2,2] 这个区间是一个值,如果是left<right,那就直接退出了,相当于忽略了2这个索引值的判断,因此也要随之进行改变,还有一个变换的点就是右边界的取值变成了mid-1,这个又是怎么理解呢,因为现在是左闭右闭区间了,即[left,right]这个时候如果mid不是要找的,就分成两个区间[left,mid-1]和[mid+1,right]这两个区间,就是现在的区间就是这两个之一了。

综上,其实就是看初始化的取值,看区间是左闭右开还是左闭右闭,然后就通过这个搜索区间去判断循环退出条件区间变换值

当你看到这,能理解的话,恭喜你,你已经掌握了二分法的大部分真谛了。

2.寻找第一个大于目标值的位置

相信这个题目也是比较清楚的,就是给你一个数组,然你去找第一个大于目标值target的位置,这个题适用在哪呢,想想看,是不是在插入排序这块就是用的这个东西。插入排序不就是在原数组中每次拿出一个值,找一下这个值应该放在哪,就是放在第一个比这个数要大的位置。接下来我给个代码再解释下就清楚多了。

一般情况下左闭右开跟左闭右闭都是可以使用的,但是习惯性问题,我下面讲的例子都采用左闭右开的方式

def search_loc(nums,target):
    '''
    这个就是找第一个比目标元素大的元素
    :param nums: 目标数组
    :param target: 目标元素
    :return: 返回的是插入位置 找到第一个比目标值大的位置
    '''
    left=0
    right=len(nums)
    while(left<right):
        mid=(left+right)>>1
        if (nums[mid]>target):
            right=mid
        else:
            left=mid+1
    return left

最关键的点就是在于这个相等的情况,其他跟寻找对应元素的索引是一样的。为啥这里当找到目标元素的时候,咱要给他left=mid+1呢。这个就是要跟题目相关联了,题目说找到比目标元素大的位置,所以说当mid值就是目标元素的时候,说明实际的位置区间应该在mid右边,就是[mid+1,right)。

这里要注意就是当mid比目标元素大,这个时候有可能这个mid就是咱要找的位置,但是现在区间变成[left,mid)乍一看这个目标值好像在现在要找的区间之外了,是不是丢了。其实不然,这个时候循环退出条件就很重要了,现在假如这个mid就是这个要求的值,那么left会一直向他靠近,直到[mid-1,mid)然后最后一次判断mid-1还是不满足,left=mid-1+1 变成mid,循环退出。返回的位置还是这个mid。 是不是感觉很神奇?那么这种情况就讲完了,大家可以尝试写一下这个左闭右闭的情况。结果是一样的,都是可以进行成功返回的。

当然这个模板也可以实现寻找目标值的右边界,啥意思呢

就是比如[1,2,3,3,3,4,5] ,target=3 用这个方法 找到的就是4的索引值,如果return left-1 那么返回的就是 3的最右边了。其实思路是一样的。改变的就是返回值减1就行了

3.寻找第一个大于等于目标值的位置(左边界)

这个也是题目的意思,还是按上面给的例子

[1,2,3,3,3,,4,5] 现在咱要要找的不是上面的那个了,要找的是第一大于等于目标值的位置,这个其实也叫左边界

适用的就是有个最长上升子序列有用到这个思路。

这个怎么做呢,先不看代码分析下,就是mid等于目标值的时候,是不是咱要找的值要么是这个mid,要么就是在他左边,所以就是进行一个右边界的收缩。就是让right=mid

def search_loc(nums,target):
    '''
    :param nums: 目标数组
    :param target: 目标元素
    :return: 返回的是插入位置 这个就是找第一个大于等于目标元素的位置
    '''
    left=0
    right=len(nums)
    while(left<right):
        mid=(left+right)>>1
        if (nums[mid]>=target):
            right=mid
        else:
            left=mid+1
    return left

其实跟上面那个右边界的区别就是nums[mid]>=target ,就是当相等的时候,进行右边界的收缩。

4.其他采用二分法的题目

Leetcode153.寻找旋转排序数组中的最小值

这里大家可以先看看里面的讲解,都很精彩,我的理解就是左闭右开跟左闭右闭还有一些区别,就是采用左闭右闭如果采用left<right的方式进行退出,会导致1.返回的值必定是在数组的长度索引之内2.就是如果只有一个元素可以直接返回不用进行判断,这是这种方式的优点,但是如果采用左闭右开的话,就是数组中只有一个元素的话,还是得进行判断。同时有可能返回数组的长度的索引,在整个数组的最外面。这是不同的题的一些区别,得看看题目中,是否隐藏了如果只有一个元素的话直接返回就可以的信息。

leetcode278. 第一个错误的版本

leetcode378. 有序矩阵中第K小的元素

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值