二分查找全解

一、简介

二分查找算法,也叫折半查找算法。二分查找的思想非常简单,有点类似分治的思想。二分查找针对的是一个有序的数据集合,每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。


二、核心思想

如果中间值大于查找值,则往数组的左边继续查找,如果小于查找值这往右边继续查找。二分查找的思想虽然非常简单,但是查找速度非常长,二分查找的时间复杂度为O(logn)。而且二分查找需要两个必要条件:

  • 用于查找的内容逻辑上来说是需要有序的
  • 查找的数量只能是一个,而不是多个

三、常规二分查找

在这里我用一道例题来引出:

Leetcode.704 二分查找

  • 首先选择数组中间的数字和需要查找的目标值比较
  • 如果相等最好,就可以直接返回答案了
  • 如果不相等
    • 如果中间的数字大于目标值,则中间数字向右所有数字都大于目标值,全部排除
    • 如果中间的数字小于目标值,则中间数字向左所有数字都小于目标值,全部排除
class Solution {
public:
    int search(vector<int>& nums, int target) {
    int left=0;int right=nums.size()-1;
    while(left<=right){
        int mid=left+(right-left)/2;
        if(nums[mid]==target){
            return mid;
        }
        else if(nums[mid]<target) left=mid+1;
        else right=mid-1;
    }
    return -1;
    }
};

第一种:left和right都是可取的(左闭右闭)

判定条件:left<=right

1.如果中间值大于target:这时right=mid-1

2.如果中间值小于target:这时left=mid+1


public:
    int search(vector<int>& nums, int target) {
    int left=0;int right=nums.size();
    while(left<right){
        int mid=left+(right-left)/2;
        if(nums[mid]==target) return mid;
        else if(nums[mid]<target) left=mid+1;
        else right=mid;
    }
    return -1;
    }
};

第二种:left可取,right不可取(左闭右开)

判定条件:left<right

1.如果中间值大于target:这时right=mid

2.如果中间值小于target:这时left=mid+1

当然还有左开右闭的写法,我在这里就不再详述了


四、二分查找拓展

当然直接让你查找值的还是少数,大多数都是以此为基础然后进行一些细微的变化,可能并不是直接让你找这个数值,可能需要你提供大于这个数值或小于这个数值的位置,我在这里提供一个二分查找的拓展(保持在左闭右闭区间内完成书写):

1.中间值等于目标值时,让left=mid+1, 那么left一定在第一个大于目标值的位置,而如果目标值存在,right在等于目标值的位置,如果目标值不存在,那么right在小于目标值的位置

2.中间值等于目标值时,让right=mid-1,那么right一定在第一个小于目标值的位置,而如果目标值存在,left在等于目标值的位置,如果目标值不存在,那么left在大于目标值的位置 

tips:运行结束left和right的位置必定相邻,并且left=right+1

Leetcode.278 第一个错误的版本

//把中间值小于等于目标值看做false,大于目标值看成true
//那么一轮循环结束后,很显然left在第一个true的位置
class Solution {
public:
    int firstBadVersion(int n) {
    int left=1;int right=n;
    while(left<=right){
        int mid=left+(right-left)/2;
        if(!isBadVersion(mid))left=mid+1;
        else right=mid-1;
    }
    return left;
    }
};

还有另外一种思路

//把中间值大于等于目标值看做true,小于目标值看成false
//那么一轮循环结束后,很显然right在最后一个false的位置,而left在第一个true的位置
class Solution {
public:
    int firstBadVersion(int n) {
    int left=1;int right=n;
    while(left<=right){
        int mid=left+(right-left)/2;
        if(isBadVersion(mid))right=mid-1;
        else left=mid+1;
    }
    return left;
    }
};

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

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
    //查找第一个出现的位置和最后一个出现的位置:查找第一个出现位置,查找最后一个出现位置
    int left=0;int right=nums.size()-1;int exist=-1;
    while(left<=right){
    int mid=left+(right-left)/2;
    if(nums[mid]==target){
        exist=mid;
        break;
    }
    else if(nums[mid]<target)left=mid+1;
    else right=mid-1;
    }
    if(exist==-1){
        return {-1,-1};
    }
    //查找第一个出现的位置
    left=0;right=nums.size()-1;
    while(left<=right){
        int mid=left+(right-left)/2;
        if(nums[mid]>=target)right=mid-1;
        else left=mid+1;
    }
    //right在第一个小于target的位置,那么left在大于或等于target的位置
    //因为检测到target存在,所以left是第一个等于target的位置
    int l=left;
    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-1;
    }
    //同理,right是最后一个等于target的位置
    int r=right;
    return{l,r};
    }
};

Leetcode.436 寻找右区间

class Solution {
public:
    vector<int> findRightInterval(vector<vector<int>>& intervals) {
    int len=intervals.size();
    vector<int>ans(len,0);//保存最终的答案
    vector<pair<int,int>>help;//储存每个区间的start
    for(int i=0;i<intervals.size();i++){
        help.emplace_back(intervals[i][0],i);
    }

    //1.midVal<=target-->left=mid+1-->left一定是第一个大于val的数,right可能小于或等于val
    //2.midVal>=target--->right=mid-1--->right一定是第一个小于val的数,left可能大于或等于val

    sort(help.begin(),help.end());
    
    //遍历intervals数组,往ans里填答暗
    for(int i=0;i<intervals.size();i++){
        int left=0;int right=help.size()-1;
        if(intervals[i][1]>help[right].first){
            ans[i]=-1;
            continue;
        }
        //需要你找大于等于target的第一个位置,那么等于时移动right,那么right在第一个小于target的位置
        //而left在第一个大于等于target的位置
        while(left<=right){
            int mid=left+(right-left)/2;
            if(help[mid].first<intervals[i][1]){
                left=mid+1;
            }
            else right=mid-1;
        }
        ans[i]=help[left].second;
    }
    return ans;
    }
};

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值