17道题带你理解二分查找

不太会的可以先看看二分查找的模板
点这里

最需要注意的是二分查找的分界线并不止局限于大于等于小于,而是对某种性质的一种分界

1.Leetcode704. 二分查找

在这里插入图片描述

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

2.Leetcode69. Sqrt(x)

在这里插入图片描述

class Solution {
public:
    int mySqrt(int x) {
       long long l=0,r=x;
       while(r>l){
           long long mid=(l+r+1)/2;
           if(mid<=x/mid){
               l=mid;
           }else{
               r=mid-1;
           }
       }  
       return l;
    }
};

3.Leetcode374. 猜数字大小

在这里插入图片描述

class Solution {
public:
    int guessNumber(int n) {
        long long  l=1,r=n;
        while(l<r){
            long long  mid=(l+r)/2;
            if(guess(mid)==0||guess(mid)==-1){
                r=mid;
            }else {
                l=mid+1;
            }
        }
        return r;

    }
};

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

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    int findMin(vector<int>& nums) {
       int len=nums.size();
       int l=0,r=len-1;
       int high=nums[len-1];
       while(l<r){
           int mid=(l+r)/2;
           if(nums[mid]<=high){
               r=mid;
           }else{
               l=mid+1;
           }
       }
       return nums[l];
    }
};

5.Leetcode33. 搜索旋转排序数组

在这里插入图片描述
这道题很特别,相较于Leetcode153.寻找旋转排序数组中的最小值
这里找的是目标值target,而不是nums[0],但是target对于153中nums[0]划分的界限(利用最后一个数x作为分界点,小于等于x在nums[0]右边,大于x在nums[0]左边)来说,在这里难以找到循环数组中target是什么性质的分界线,还是需要借助nums[0]和target的位置关系,直接分情况讨论

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int len=nums.size(),ans;
        int st=find1(nums,0,len-1,nums[len-1]);
        printf("st=%d\n",st);
        if(nums[len-1]>=target){
            ans=find2(nums,st,len-1,target);
        }else{
            ans=find2(nums,0,st-1,target);
        } 

        if(nums[ans]!=target)return -1;
        return ans;
        
    }
    //查找nums[0]的位置,nums[0]的左边都>nums[len-1],右边都小于等于nums[len-1]
    int  find1(vector<int>&nums,int l,int r,int divide){
        while(l<r){
            int mid=(l+r)/2;
            if(nums[mid]<=divide){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return l;

    }
    //查找target位置,此时查找区间一定是递增的
    int find2(vector<int>&nums,int l,int r,int divide){
        while(l<r){
            int mid=(l+r)/2;
            if(nums[mid]>=divide){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return l;

    }
};

6.Leetcode278. 第一个错误的版本

在这里插入图片描述

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        long long l=1,r=n;
        while(l<r){
            long long mid=(l+r)/2;
            if(isBadVersion(mid)){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return l;
    }
};

7.Leetcode162. 寻找峰值

在这里插入图片描述
参考Leetcode作者:guanpengchn
首先要注意题目条件,在题目描述中出现了 nums[-1] = nums[n] = -∞,这就代表着只要数组中存在一个元素比相邻元素大,那么沿着它一定可以找到一个峰值
往递增的方向上,二分,一定能找到波峰,往递减的方向只是可能找到,也许没有。
根据上述结论,我们就可以使用二分查找找到峰值
查找时,左指针 l,右指针 r,以其保持左右顺序为循环条件
根据左右指针计算中间位置 m,并比较 m 与 m+1 的值,如果 m 较大,则左侧存在峰值,r = m,如果 m + 1 较大,则右侧存在峰值,l = m + 1

时间复杂度O(logN)

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int len=nums.size();
        int l=0,r=len-1;
        int ans;
        while(l<r){
            int mid=(l+r)/2;
            if(nums[mid]>nums[mid+1]){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return l;
    }
};

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

在这里插入图片描述

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
         vector<int>ans;
         if(nums.size()==0){
             ans.push_back(-1);
             ans.push_back(-1);
             return ans;
         }
         int l=findl(nums,target);
         int r=findr(nums,target);
         if(l==-1||r==-1){
            ans.push_back(-1);
            ans.push_back(-1);
         }else{
             ans.push_back(l);
             ans.push_back(r);
         }
         return ans;

    }
    int findl(vector<int>&nums,int target){
        int l=0;int r=nums.size()-1;
        while(l<r){
            int mid=(l+r)/2;
            if(nums[mid]>=target){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        if(nums[l]==target)return l;
        return -1;
    }
    int findr(vector<int>&nums,int target){
        int l=0;int r=nums.size()-1;
        while(l<r){
            int mid=(l+r+1)/2;
            if(nums[mid]<=target){
                l=mid;
            }else{
                r=mid-1;
            }
        }
        if(nums[l]==target)return l;
        return -1;
    }
};

9.Leetcode658. 找到 K 个最接近的元素

在这里插入图片描述
法一:直观地,我们可以将数组中的元素按照与目标 x 的差的绝对值排序,排好序后前 k 个元素就是我们需要的答案。
法二:
①如果target<nums[0],返回前k个数
②如果target>nums[len-1],返回后k个数
③利用二分找到最接近的数,然后不断比较–left,–right与target的绝对值,直到找够k个最小的

class Solution {
public:
    const int N=1e4+10;
    vector<int>ans;
    vector<int> findClosestElements(vector<int>& arr, int k, int x) {
        int len=arr.size();
        if(x<=arr[0]){
            int i=0;
            while(k--){
              ans.push_back(arr[i++]);
            }
        }else if(x>=arr[len-1]){
            int i=len-1;
            while(k--){
              ans.push_back(arr[i--]);
            }
        }else{
            int index=find(arr,x);
            //找到最接近的值的下标
            if(index-1>=0&&abs(arr[index-1]-x)<=abs(arr[index]-x))index--;//左优先,所以这里是小于等于
            if(index+1<len&&abs(arr[index+1]-x)<abs(arr[index]-x))index++;
           
            ans.push_back(arr[index]);
            int l=index-1,r=index+1;
            while(--k&&l>=0&&r<len){
                if(abs(arr[l]-x)<=abs(arr[r]-x)){
                    ans.push_back(arr[l--]);
                }else{
                     ans.push_back(arr[r++]);
                }
            }
            while(k--){
                if(l>=0)ans.push_back(arr[l--]);
                if(r<len)ans.push_back(arr[r++]);
            }
            

        }
        sort(ans.begin(),ans.end());
        return ans;
        

    }
    int find(vector<int>&arr,int x){
          int l=0,r=arr.size()-1;
          while(l<r){
              int mid=(l+r)/2;
              if(arr[mid]>=x){
                  r=mid;
              }else{
                  l=mid+1;
              }
          }
          return l;
    }
};

10.Leetcode50. Pow(x, n)

在这里插入图片描述
这道用快速幂好一些

class Solution {
public:
    const double INF=0x3f3f3f3f;
    double myPow(double x, long n) {
       bool flag=false;
       if(n<0)flag=true;
       if(flag)n=-n;
       double res=qmi(x,n);
       if(flag)return 1/res;
       return res;
    }
    double qmi(double m,long k){
       double res=1,t=m;
       while(k){
           if(k&1)res=res*(double)t;
           t=t*t;
           k=k>>1;
       }
       return res;
    }
};

11.Leetcode367. 有效的完全平方数

在这里插入图片描述

class Solution {
public:
    bool isPerfectSquare(int num) {
        long long l=1,r=num;
        while(l<r){
            long long mid=(l+r)/2;
            if(mid>=num/mid)r=mid;
            else l=mid+1;
        }
        if(l*l==num)return true;
        return false;

    }
};

12.Leetcode744. 寻找比目标字母大的最小字母

在这里插入图片描述

class Solution {
public:
    char nextGreatestLetter(vector<char>& letters, char target) {
        int len=letters.size();
        int l=0,r=len-1;
        while(l<r){
            int mid=(l+r)/2;
            if(letters[mid]>target){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        if(l==len-1&&target>=letters[l])return letters[0];
        return letters[l];
    }
};

13.Leetcode154. 寻找旋转排序数组中的最小值 II

14.Leetcode349. 两个数组的交集

在这里插入图片描述
法一:hash
法二:排序加双指针
法三:排序+二分

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
         sort(nums1.begin(),nums1.end());
         sort(nums2.begin(),nums2.end());
         int len1=nums1.size();
         int len2=nums2.size();
         vector<int>ans;
         for(int i=0,j=0;i<len1;i++){
             j=i;
             while(j<len1-1&&nums1[j]==nums1[j+1])j++;
             bool res=search(nums1[j],nums2,len2);
             if(res)ans.push_back(nums1[j]);
             i=j;
         }
         return ans;

    }
    bool search(int target,vector<int>& nums2,int len){
        int l=0,r=len-1;
        while(l<r){
            int mid=(l+r)/2;
            if(nums2[mid]>=target)r=mid;
            else l=mid+1;
        }
        if(nums2[l]==target)return true;
        return false;

    }
};

15.Leetcode350. 两个数组的交集 II

在这里插入图片描述

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
         int len1=nums1.size();
         int len2=nums2.size();
         if(len1>len2){
             return intersect(nums2,nums1);
         }
         unordered_map<int,int>m;
         for(int num:nums1){
             ++m[num];
         }
         vector<int>ans;
         for(int num:nums2){
             if(m.count(num)){
                 ans.push_back(num);
                 --m[num];
                 if(m[num]==0)m.erase(num);
             }
         }
         return ans;     

    }
};

16.Leetcode167. 两数之和 II - 输入有序数组

在这里插入图片描述

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        vector<int>ans;
       int len=numbers.size();
       int i=0,j=len-1;
       int prei=0,prej=len-1;
      
       while(i<=j){
          if(numbers[i]+numbers[j]==target){
              ans.push_back(i+1);
              ans.push_back(j+1);
              break;
          }else if(numbers[i]+numbers[j]<target){
              i++;
              if(i==prei)break;
              prei=i;
              
          }else{
              j--;
              if(j==prej)break;
              prej=j;
          }
       }
       return ans;
    }
};

17.Leetcode287. 寻找重复数

根据抽屉原理,我们定义cnt[i]表示nums 数组中小于等于 i 的数有多少个.
假设我们重复的数是 target,那么 [1,target−1]里的所有数满足 cnt[i]≤i,[target,n] 里的所有数满足 cnt[i]>i,具有单调性。

数字是在1~ n之间的乱序,给的数组长度是n+1,所以通过抽屉原理可知:必定有一个数字重复。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int len=nums.size();
        sort(nums.begin(),nums.end());
        int l=0,r=len-1;
        while(l<r){
            int mid=(l+r)/2;
            int cnt=0;
            for(int i=0;i<len;i++){
                cnt+=nums[i]<=mid;
            }
            // 数的范围为1~n,1~n中小于等于4的数只有1,2,3,4一共4个,根据抽屉原理,小于等于 4 的个数如果严格大于 4 个,此时重复元素一定出现在 [1..4] 区间里
            if(cnt>mid){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return nums[l];
    }
};
  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值