二分查找专题

二分查找专题

性质

  • 本质:不是单调性,而是可以找到一个性质,可以把整个区间一分为二,一半满足这个性质,一半不满足 --> 二分就是寻找这个能把区间一分为二的边界
  • 思路: 寻找某种特性, 使得整个区间被一分为二, 每次通过确认要找的元素在哪个区间内, 就可以直接排除掉另一半区间
  • 有单调性一定可以二分,但是没有单调性也可能可以二分
  • 二分时如果不确定边界(比如要不要等于), 可以自己举个实例试一下

二分查找模板

  1. 模板一
    此模板将区间分为[l,mid-1]+[mid,r]
int find(vector<int>& nums){
    int l=0,r=nums.size()-1,mid;
    while(l<r){
        mid=(l+r+1)/2;
        if(check(mit)) l=mid;
        else r=mid-1;
    }
    return l;
}
  1. 模板二
    此模板将区间分为[l,mid]+[mid+1,r]
int find(vector<int>& nums){
    int l=0,r=nums.size()-1,mid;
    while(l<r){
        mid=(l+r)/2;
        if(check(mid)) l=mid+1;
        else r=mid;
    }
    return l;
}

例题

  1. 搜索插入位置 https://leetcode.cn/problems/search-insert-position/?envType=study-plan-v2&id=top-100-liked
  • 这题思路很简单, 整个区间可以一分为二, 一半小于目标值, 一半大于等于目标值
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l=0,r=nums.size()-1;
        int mid;
        while(l<r){ //左边的集合: <target;  右边的集合: >=target  区间[l,r]被划分成[l,mid-1]和[mid,r]
            mid=(l+r+1)/2;
            if(nums[mid]<target) l=mid;  //如果target>nums[mid], 说明target在右边的集合
            else r=mid-1;
        }
        if(nums[l]<target) return l+1;
        return l;
    }
};
  1. 搜索二维矩阵 https://leetcode.cn/problems/search-a-2d-matrix/?envType=study-plan-v2&id=top-100-liked
  • 这题实际上和上一题是一样的, 可以把二维看作一维(进行坐标转换就行)
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m=matrix.size();
        int n=matrix[0].size();
        int l=0,r=m*n-1,mid;
        while(l<r){
            mid=(l+r+1)/2;
            int x=mid/n,y=mid%n;
            cout<<x<<" "<<y<<endl;
            if(matrix[x][y]<=target) l=mid;
            else r=mid-1;
        }
        int x=l/n,y=l%n;
        cout<<l<<endl;
        return matrix[x][y]==target;
    }
};
  1. 在排序数组中查找元素的第一个和最后一个位置 https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/?envType=study-plan-v2&id=top-100-liked
  • 这题实际上用了二分查找的两个模板。具体思路:
    • 找第一个位置:可以把整个区间分为两部分, 左半部分小于target, 右半部分>=target,找右半部分起点,即target的起点
    • 找最后一个位置: 左半区间小于等于target, 右半区间大于target,找左半部分终点
class Solution {
public:
    int LeftSearch(vector<int>& nums,int target){ //把[l,r]分为[l,mid]+[mid+1,r], 左区间<target, 右区间>=target
        int l=0,r=nums.size()-1,mid;
        while(l<r){
            mid=(l+r)/2;
            if(nums[mid]<target) l=mid+1; //要找的答案在右区间
            else r=mid;
        }
        return l; //target第一次出现的位置
    }

    int RightSearch(vector<int>& nums,int target){ //把[l,r]分为[l,mid-1]+[mid,r], 左区间<=target, 右区间>target
        int l=0,r=nums.size()-1,mid;
        while(l<r){
            mid=(l+r+1)/2;
            if(nums[mid]<=target) l=mid; //要找的答案在右区间
            else r=mid-1;
        }
        return l;
    }

    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.empty()) return {-1,-1};
        int first_appear=LeftSearch(nums,target);
        if(nums[first_appear]!=target) return {-1,-1};
        int last_appear=RightSearch(nums,target);
        return {first_appear,last_appear};
    }   
};
  1. 搜索旋转排序数组 https://leetcode.cn/problems/search-in-rotated-sorted-array/?envType=study-plan-v2&id=top-100-liked
  • 解法一:暴力搜索

    (这里真的要吐槽一下,遍历就能过吗,这还能打败70%的人吗,什么算法都不用也可以叫中等题吗)
class Solution {
public:
    int search(vector<int>& nums, int target) {
        for(int i=0;i<nums.size();i++){
            if(nums[i]==target) return i;
        }
        return -1;
    }
};
  • 解法二:二分
    • 由于旋转完的数组一定满足几个性质:整个数组由两个严格递增子数组组成+前一个子数组一定大于后一个子数组, 因此可以二分,每次判断一下mid左边还是右边是严格递增的数组。而通过判断出的这个严格递增数组,通过check一下target在不在它的范围内,就可以得出target应该在这个数组,还是在另一个数组。(因为这个严格递增数组的范围是确定的,通过比较上界和下界就可以确定target在不在其中;而另一个数组不是严格递增,无法确认其范围:最大值可以通过比较该数组的首尾元素确认,但无法确认最小值是多少)
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l=0,r=nums.size()-1,mid;
        while(l<=r){
            mid=(l+r)/2;
            if(nums[mid]==target) return mid;
            //根据mid可以判断单调区间在[l,mid]还是[mid,r]
            if(nums[l]<=nums[mid]){  //[l,mid]一定是递增的
                if(target>=nums[l]&&target<nums[mid]){  //target在[l,mid]
                    r=mid-1; //没必要考虑mid, 因为nums[mid]一定不等于target, 否则就会在之前被返回
                }else{ //target在[mid,r]
                    l=mid+1; //没必要考虑mid, 因为nums[mid]一定不等于target, 否则就会在之前被返回
                }
            }else{ //[mid,r]一定是递减的
                if(target>nums[mid]&&target<=nums[r]){
                    l=mid+1;
                }else{
                    r=mid-1;
                }
            }
        }
        return -1;
    }
};
  1. 寻找旋转排序数组中的最小值 https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/?envType=study-plan-v2&id=top-100-liked
  • 这题只要找到将区间一分为二的性质,剩下的思路和代码实现就非常简单(虽然思路没想出来)。

    首先要明确数组旋转的定义:把末尾元素移到数组头, 即[0,1,2,,n-1]-->[n-1,0,1,2,,n-2]。因此旋转k次后, 应该得到[n-k+1,n-k,,0,1,2,,n-k]。因此可以通过最小值m把数组一分为二:[n-k+1,n-k,,m]+[m+1,m+2,,n-k]。这两个区间满足一个性质: 考虑数组的最后一个元素(n-k), 在最小值左边的元素(不包括最后一个)一定严格小于n-k, 而最小值左边的元素一定严格大于n-k。而每次比较的时候, 若中点值<最后一个元素, 说明这个中点值一定在最小值右边,最小值在左区间。
class Solution {
public:
    int findMin(vector<int>& nums) {
        int l=0,r=nums.size()-1,mid;
        while(l<r){
            mid=(l+r)/2;
            if(nums[mid]<nums[r]){ //说明mid在最小值右边, 最小值在左区间
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return nums[l];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值