二分模板(判断条件设置 收缩边界 返回值判断)

二分法一不小心就会写成死循环,虽然边界确定是门学问,但是对我来说,每次收缩边界的判断条件也很头大,因此就有序数组(向量也行)二分的常见情况和常见变形进行总结。

前提:本文二分区间均是左闭右闭,即左下标等于右下标也合法

区间也遵循寻找key值在左闭右闭区间内的原则收缩。

​ 本文右闭区间的n代表 数组.size()-1,也就是数组的最右边界下标。

[1]寻找某数 key

判断条件设置(设为三段):

  • 相等判断(数组[mid] == key)

    ​ 成立即返回当前下标

  • 小于判断(数组[mid] < key)

​ 由于闭区间,淘汰当前数组中mid及左侧的数,区间收缩为[mid+1, n]

  • 大于判断(前两种情况的剩余情况)

​ 由于闭区间,淘汰当前数组中mid及右侧的数,区间收缩为 [0, mid-1]

返回处理(跳出左闭<=右闭条件循环):

​ 由于闭区间特性,如果满足 数组[mid] == key 就会在循环中直接跳出,如果走到left > right 也就是循环外面,说明没找到,返回-1。

模板&练手题:

		int l = 0; 
        int r = n;
        while(l <= r)
        {
            int mid = l + ((r - l)>>1);
            if(key == nums[mid])
            return mid;
            else if(key < nums[mid])
            {
                r = mid - 1;
            }
            else{
                l = mid + 1;
            }
        }
        return -1;

力扣704.二分查找

[2]寻找第一个大于等于某数 key的数

判断条件设置(设为两段):

  • 仍需压榨( key <= 数组[mid] )

​ 已经找到大的或者等的,仍要尝试根据从小到大的有序性看能否向左继续压榨看看有没有 大但较现在更小的或者等但位置更靠前的,因为是第一个),区间收缩为[0, mid-1]

  • 仍未找到(前种情况的剩余情况)

​ 下限left不断向right 也就是数组尾移动但是right不动, 区间收缩为[mid+1, n]

返回处理(跳出左闭<=右闭条件循环):

  • 没找到left一直提升,直到右边界之外也就是n+1地方,此时应该返回-1。
  • 如果找到就是返回left

​ 终止时之前如果是left==right,此时left一定==mid且是数组存在key值时,因为mid==(left+right)/2,返回的其实就是找到的mid。

​ 终止之前如果是key <= 数组[mid] ,是满足要求的最左,但此时right仍会左移,left返回的也是要找的答案。

​ 终止之前如果是key > 数组[mid],说明右边界上次缩的太狠了,应该回到没缩之前,此时按条件向右移动左边界,left就是最左被返回。

模板&练手题:

		int l = 0; 
        int r = n;
        while(l <= r)
        {
            int mid = l + ((r - l)>>1);
            if(key <= nums[mid])
            {
                r = mid - 1;
            }
            else{
                l = mid + 1;
            }
        }
        return (low <= n)? low : -1;

力扣35. 搜索插入位置

[3]寻找第一个大于某数 key的数

判断条件设置和返回处理逻辑同前文[2], 只不过key == 数组[mid]时不能归为仍需压缩条件,因为条件已经不满足了,所以归为其余情况。对应改模板的条件划分即可。

模板&练手题:

		int l = 0; 
        int r = n;
        while(l <= r)
        {
            int mid = l + ((r - l)>>1);
            if(key < nums[mid])
            {
                r = mid - 1;
            }
            else{
                l = mid + 1;
            }
        }
        return (low <= n)? low : -1;

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

基于三个模板的延伸

题目延伸是改变或者增加最后返回处理的条件判断

1)找到目标数key位于有序数组位置最小下标,没有返回-1

​ 基于前文[2], 但[2]可能是大于该数最小(左)的数,故要增加判断条件看是否应返回-1:

数组[下标] == key

2)查找到key满足条件对应下标的前一个位置。
  1. 找目标数key出现位置的最大下标, 没有则返回-1(类似于[1,2,2,4,8]中找数组中第二个2的下标位置并返回)

    ​ 基于上述模板[3]找到的是第一个比key大的数的下标left,那么嫌疑值就在left - 1, 但是比 比key大的第一个数小的 最大数不一定等于key。而我们仍要判断left-1是否合法会越左界,即left - 1 > 0(不越右界是因为最右只能到n+1, 而left-1为n合法), 因此增加两个条件看是否返回-1:

    left - 1 > 0

    数组[下标] == key

  2. 找小于目标数key的最大下标, 没有则返回-1(类似于[1,2,2,4,8]中找1的下标并返回)

    ​ 基于上述模板[2],只需增加一个判断条件看是否返回-1:

    left - 1 > 0

  3. 找目标数key在数组中出现的次数

    ​ 上界 :基于[3] 为右界last

    ​ ===>次数为last - first

    ​ 下界 : 基于[2] 为左界first

    此时不需要增加条件判断找不到就返回-1,找不到就应该到n+1,如果是-1会造成误解,每次求上下界的返回处理不处理直接返回即可:

    return left

    总结

    ​ 最后会发现所有模板其实都有相似之处,最头大的收缩边界也被统一成 满足条件设置 right = mid - 1,不满足则left = mid + 1(除了明确寻找某数key设三个判断)。包括如果是浮点数二分也只是将l <= r改成 r - l >= 设置极小精度误差(如1e-7),边界直接收缩不增减1,可参考Acwing数的三次方这里不多赘述。

参考文章:https://blog.csdn.net/yefengzhichen/article/details/52372407

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值