CC52.【C++ Cont】二分查找的左、右边界模版

目录

1.例题

分析

方法1:暴力查找

方法2:利用数组有序和二段性来优化

二段性:分两段,舍一段,操作另一段(这个是二分查找的本质!)

找左边界(开始位置)

普通二分法需要改进,分两类讨论

细节处理

1.循环条件是left

2.写left<=right会死循环

3.更新mid的公式

找右边界(结束位置)

代码

2.查找左边界的二分模版(万能)

3.查找右边界的二分模版(万能)


承接CC53.【C++ Cont】二分查找的普通模版文章继续介绍二分法的另外两个模版,比较重要

1.例题

以LeetCode的34. 在排序数组中查找元素的第一个和最后一个位置题为例:

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

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • nums 是一个非递减数组
  • -10^9 <= target <= 10^9

分析

理解非递减的含义:值增长或者值不变,以[5,7,7,8,8,10], target = 6示例来画图:

方法1:暴力查找

两个指针left和right分别从左和从右遍历,找到target就退出循环,时间复杂度为O(N)

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        vector<int> ret={-1,-1};
        int left=0,right=nums.size()-1;
        for (;left<nums.size();left++)
            if (nums[left]==target)
            {
                ret[0]=left;
                break;
            }
                
        for (;right>=0;right--)
            if (nums[right]==target)
            {
                ret[1]=right;
                break;
            }
        return ret;   
    }
};

提交结果:

方法2:利用数组有序和二段性来优化

二段性的具体概念参见CC53.【C++ Cont】二分查找的普通模版文章,这里直接给出二段性的总结:

二段性:分两段,舍一段,操作另一段(这个是二分查找的本质!)

找左边界(开始位置)

给定以下数组,设蓝色区域元素的值小于目标值t,绿色区域元素的值大于等于目标值t

如果尝试使用之前CC53.【C++ Cont】二分查找的普通模版文章将的普通二分法的模版来求左边界会有问题,例如设红色区域元素的值都等于目标值t

则mid指向的元素不一定符合左边界的要求

则mid还要向左查找,特殊情况下时间复杂度为O(N),例如[5,5,5,5,5,5,5] target=5

普通二分法需要改进,分两类讨论

1.nums[mid]<t

根据二段性:舍一段,操作另一段

更新left为mid+1

left=mid+1;

2.nums[mid\geqslantt

根据二段性:舍一段,操作另一段

更新right为mid

right=mid;

注意不是mid+1,极端情况下当mid正好指向左边界时,更新right为mid-1会错过左边界!!!

细节处理
1.循环条件是left<right

当left=right时,就是最终结果,要么等于目标值t,返回下标,要么不等于目标值t,分类讨论数组的所有情况

情况1:能找到目标值t

通过left和right的移动策略来看:

left=mid+1;
right=mid;

left永远想离开<t的区域(因为mid+1),right永远在\geqslantt的区域,最终情况一定是left==right,此时left和right都指向左边界

情况2:找不到目标值t,且所有元素大于t

nums[mid]\geqslantt,right=mid,left不动,最终情况是left==right在区间的最左边,但此时left和right指向的值不等于目标值t

情况3:找不到目标值t,且所有元素小于t

nums[mid]<t,left=mid+1,right不动,最终情况是left==right在区间的最右边,但此时left和right指向的值不等于目标值t

2.写left<=right会死循环

情况1中当left=right=左边界时,nums[mid]\geqslantt,left==right==mid,left和right都不动

3.更新mid的公式

之前在文章中提到过更新mid的公式,有两种写法:

mid=left+(right-left)/2;
mid=left+(right-left+1)/2;

对于查找左边界而言,当left+1==right时,mid=left+(right-left+1)/2==right,当nums[mid],nums[mid]\geqslantt,right=mid,仍然有mid=right,程序死循环

因此只能使用mid=left+(right-left)/2;

找右边界(结束位置)

和找左边界类似,设蓝色区域元素的值小于等于目标值t,绿色区域元素的值大于目标值t

 1.nums[mid]\leqslantt

根据二段性:舍一段,操作另一段

left=mid;

2.nums[mid]>t

根据二段性:舍一段,操作另一段

right=mid-1;

循环条件:left<right,更新mid使用mid=left+(right-left+1)/2;,不再证明

代码

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        vector<int> ret={-1,-1};
         if (nums.size()==0)
            return ret;
        int left=0,right=nums.size()-1;
        //能进入循环的前提是数组不为空!!!
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if (nums[mid]<target)
                left=mid+1;
            else
                right=mid;
        }
        if (nums[left]==target)
            ret[0]=left;
        
        left=0,right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left+1)/2;
            if (nums[mid]<=target)
                left=mid;
            else
                right=mid-1;
        }
        if (nums[left]==target)
            ret[1]=left;
        return ret;   
    }
};

注找完左边界后其实left没有必要置为0,这里因为格式统一写上了

提交结果:

2.查找左边界的二分模版(万能)

//能进入循环的前提是数组不为空!!!
while (left < right)
{
	int mid = left + (right - left) / 2;
	if (......)
		left = mid + 1;
	else
		right = mid;
}

空缺处填上对应的条件即可

3.查找右边界的二分模版(万能)

//能进入循环的前提是数组不为空!!!
while (left < right)
{
	int mid = left + (right - left + 1) / 2;
	if (......)
		left = mid;
	else
		right = mid - 1;
}

空缺处填上对应的条件即可 

记忆:

分类讨论的代码if (......){...}else{...}就题论题即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangcoder

赠人玫瑰手有余香,感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值