二分查找的坑点与总结

以下是二分查找的标准写法
以下这个函数是二分查找nums中[left,right)部分,right值取不到,如果查到的话,返回所在地,如果查不到则返回最后一个小于target值得后一个位置。

//右值点不能取到的情况
    int binary_search(vector<int>& nums,int left,int right, int target) { 
    //坑点(1)right究竟能不能取到的问题,这里是不能取到的情况
        int i = left;
        int j= right;
        while(i<j){
            int mid = i+(j-i)/2;             //坑点(2)这里尽量这么写,因为如果写成(i+j)/2则有溢出的风险
            if(nums[mid]>=target)        //坑点(3)这个地方大于还是大于等于要依据情况而定
                j = mid;            //坑点(4)因为右值点反正不能取到,所以j就可以等于mid
            else
                i = mid+1;           //坑点(5)
        }
        return i;
    }

//右值点能取到的情况
    int searchInsert(vector<int>& nums,int left,int right, int target) {
        int i = left;
        int j= right;
        while(i<=j ){
            int mid = i+(j-i)/2;
            if(nums[mid]>=target)
                j = mid-1;
            else
                i = mid+1;
        }
        return i;
    }

一般来说写的二分查找的标准程序应该是右边right值取不到的情况,所以while循环中要加一步判断i是否小于等于nums.size();

A. 对于坑点2,如果是右值点可以取到情况下,必须是i<=j否则会出现查找偏差,比如数组中就一个元素[3],让查找5的位置,理应返回1,但是此时会输出成0,
B. 对于坑点3,主要应用在如果数组中有重复元素,需要判断其具体要返回什么位置。
C. 对于坑点,5,如果i不加1的,比如i = 0,j= 1 的情况,会出现mid = 0然后一直死循环下去
D. 对于坑点4,依据right能不能取到而定,如果right可以取到则,right必须要-1,不减1的话,还是会出现i = j时的死循环。

另外一种二分查找,今天面试头条的时候,被要求写一个二分查找,原本以为足够的简单,但是发现坑点还是有很多的,比如面试官问,如果数组中出现了重复元素该怎么办?怎么返回重复元素的第一个位置,如果查不到则返回-1,后来想了想写了个这个方法

int searchInsert(vector<int>& nums, int target) {
    int i = 0;
    int j = nums.size() - 1;
    while (i < j) {
        int mid = i + (j - i) / 2;
        if (nums[mid] > target)
            j = mid - 1;
        else
            if (nums[mid] == target)      //即继续二分查找查找相等元素的左边界即可
                j = mid;
            else
                i = mid + 1;
    }
    if (nums[i] == target)
        return i;
    else
        return -1;
}

这个有一个简化的写法,减少比较次数【注意以下写法只适合返回最开始出现的位置,如果题目改成出现的最后的一个位置,则还是得用上面的方法】

int binarySearch(vector<int> &array, int target) {
       // write your code here
    int i = 0;
    int j = array.size() - 1;
    while (i < j) {
        int mid = i + (j - i) / 2;
        if (array[mid] >= target)
            j = mid ;
        else
            i = mid + 1;
    }
    if (array[i] == target)
        return i;
    else
        return -1;
 }

二分查找的变种

leetcode 153.Find Minimum in Rotated Sorted Array
就是一个排好序的数组,然后将其平移之后,找最小的元素。
诸如[3 4 5 6 7 1 2]

 int findMin(vector<int> &num) {
        int start=0,end=num.size()-1;

        while (start<end) {
            if (num[start]<num[end])
                return num[start];

            int mid = (start+end)/2;

            if (num[mid]>=num[start]) {  //这一步为什么要≥是因为如果mid = start的情况
                start = mid+1;
            } else {
                end = mid;
            }
        }

        return num[start];
    }

//更简洁的做法,比右边,因为最右边的必然严格小于最左边,所以等于上面的方法比了一个次小的
    int findMin(vector<int>& nums) {
        int i = 0, j = nums.size()-1;
        while(i<j){
            int mid = (i+j)/2;
            if(nums[mid]>nums[j])
                i =mid+1;
            else
                j = mid;
        }
        return nums[i];
    }


  1. Find Minimum in Rotated Sorted Array II
    它的进阶形式,如果有重复元素的话,该如何呢
    要考虑到这种情况[3 3 3 3 3 3 3 3 1 3 ] [1 1 1 1 1 1 1 1 1 1 2 1 1]
    int findMin(vector<int>& nums) {
        int i = 0, j = nums.size()-1;
        while(i<j){
            int mid = (i+j)/2;
            if(nums[mid]>nums[j])
                i =mid+1;
            else
                if(nums[mid]<nums[j])
                    j = mid;
                else
                    j--;
        }
        return nums[i];
    }
  //但实际上这种方法是有一点小问题的,虽然在这个问题中体现不出来,比如就是如果最小值是重复的,且最小值横跨数组左右两个端点的时候,诸如[1 1 1 1 1 1 1 1 1 1 2 1 1]这种情况,它找出的最小值的位置其实严格意义上不算最小值的位置。     [*]
  1. Search in Rotated Sorted Array
    就是在平移数组中找到一个目标数target,找不到则返回-1,找到返回下标
//这道题一开始的思路是直接二分,然后,后来看了解答,就是用上面的方法找出最小值,然后最小值的位置也就意味着其右平移的距离,所以先假设数组没有平移,二分查找,mid的位置给换成现在数组的位置 rot_mid = (mid+rotate)%n
    int search(vector<int>& nums, int target) {
        int i=0,j= nums.size()-1;
        while(i<j){
            int mid = (i+j)/2;
            if( nums[mid]>nums[j])
                i = mid+1;
            else
                j = mid;
        }
        int rot = i;
        i = 0;
        j = nums.size()-1;
        while(i<=j){   //注意这个地方必须是<=,因为[1] 找1,如果是<的话就不行了。
            int mid = (i+j)/2;
            int rot_mid = (mid+rot)%nums.size();
            if(nums[rot_mid] == target) return rot_mid;
            if(nums[rot_mid]>target)
                j = mid-1;
            else
                i = mid+1;
        }
        return -1;
    }

进阶形式,就是数组中有重复元素了怎么办
81. Search in Rotated Sorted Array II

    bool search(vector<int>& nums, int target) {
        int i = 0,j = nums.size()-1;
        while(i<j){
            int mid = (i+j)/2;
            if(nums[mid]>nums[j])
                i = mid+1;
            else
                if(nums[mid]<nums[j])
                    j = mid;
                else
                    j--;
        }
//鉴于[*]处的分析,所以这种最小值是重复的,且横跨首尾的情况,需要额外处理
        if(i == 0 && nums[i] == nums[nums.size()-1]){
            i = nums.size()-1;
            while(i>0 &&nums[i-1] == nums[i]) i--;
        }

        int rot = i;
        i = 0;
        j = nums.size()-1;
        while(i<=j){
            int mid = (i+j)/2;
            int rot_mid = (mid+rot)%nums.size();
            if(nums[rot_mid] == target) return true;
            if(nums[rot_mid]>target)
                j = mid-1;
            else
                i = mid+1;
        }
        return false;
    }

或者采用我一开始的直接二分判断的方法

    bool search(vector<int>& nums, int target) {
        int i = 0,j = nums.size()-1;
        while(i<=j){
            int mid = (i+j)/2;
            if(nums[mid] == target)
                return true;
            if(nums[mid]>nums[j])
                if(target>nums[mid] ||target<=nums[j])
                    i = mid+1;
                else
                    j = mid-1;
            else
                if(nums[mid]<nums[j])
                    if(target>nums[mid] && target<=nums[j])
                        i = mid+1;
                    else
                        j = mid-1;
                else
                    j--;
        }
        return false;
    }

除此之外,还可参考这个博客:如何写出正确的二分查找?——利用循环不变式理解二分查找及其变体的正确性以及构造方式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值