七种二分查找问题总结

今天刷LeetCode的时候,遇到了一道二分查找的题目。代码轮廓很容易就写出来了,但是,调边界条件却调了很久。原因还是因为自己对二分查找掌握的不够透彻。于是又查了一些资料,看了别人的一些博客,再次学习了二分查找~下面总结今天看的七种二分查找的类型,代码轮廓大致一样,大多是是判断条件不一样。

注:以下讨论全是在不严格递增数组下实现的,例如,array[] = {1,2,2,3,3,4,4,5,6,7}

1. 基础版——查找有序数组中key的下标

查找有序数组中key的下标,若存在多个,则返回任意一个下标,不存在该数的话则返回-1。

解析:当我们找到array[mid] == key时,我们应该返回mid的值。当我们找到array[mid] > key时,那么我们需要求的元素应该在array[mid]之前,所以将end指针赋值为mid - 1。当我们找到array[mid] < key时,那么我们需要求的元素应该在array[mid]之后,所以将start指针赋值为mid + 1

//非递归版
int binSearchIndex(int array[] , int len , int key){
    int start = 0 , end = len - 1 , mid = 0;
    while(start <= end){
        mid = start + ((end - start ) >> 1);
        if(array[mid] == key){
            return mid;
        }else if(array[mid] < key){
            start = mid + 1;
        }else{
            end = mid - 1;
        }
    }
    return -1;
}

//递归版
int binSearchIndexRecursion(int array[] , int start , int end , int key){
    int mid = (start + end) >> 1;
    if(array[mid] == key){
        return mid;
    }
    if(array[mid] > key){
        binSearchRecursion(array , start , mid - 1 , key);
    }else if(array[mid] < key){
        binSearchRecursion(array , mid + 1 , end , key);
    }
}
2. 基础版——查找有序数组中第一个大于等于key的下标

查找有序数组中第一个大于等于key的下标,如果不存在,则返回-1。

解析:当我们找到array[mid] >= key时,我们应该假设它不是第一个大于等于key的元素,那么我们需要求的元素应该在array[mid]之前,所以将end指针赋值为mid - 1。当我们找到array[mid] < key时,那么我们需要求的元素应该在array[mid]之后,所以将start指针赋值为mid + 1。这里我们应该判断一下最后返回的start值有没有超过数组的长度,如果超过了,说明不存在大于等于key的元素,应该返回-1。

//非递归版本
int binSearch(int array[] , int len , int key){
    int start = 0 , end = len - 1 , mid = 0;
    while(start <= end){
        mid = start + ((end - start) >> 1);
        if(array[mid] >= key){
            end = mid -1 ;
        }else {
            start = mid + 1;
        }
    }
    return start < len ? start : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
    int mid = start + ((end - start) >> 1);
    if(start > end){
        return start < len ? start : -1;
    }
    if(array[mid] >= key){
        binSearchRecursion(array , start , mid - 1 , len , key);
    }else{
        binSearchRecursion(array , mid + 1 , end , len , key);
    }
}
3. 基础版——查找有序数组中第一个大于key的下标

查找有序数组中第一个大于key的下标,如果不存在,则返回-1。

解析:当我们找到array[mid] <= key时,那么我们需要求的元素应该在array[mid]之后,所以将start指针赋值为mid + 1。当我们找到array[mid] > key时,我们应该假设它不是第一个大于key的元素,那么我们需要求的元素应该在array[mid]之前,所以将end指针赋值为mid - 1。这里我们应该判断一下最后返回的start值有没有超过数组的长度,如果超过了,说明不存在大于key的元素,应该返回-1。

//非递归版本
int binSearch(int array[] , int len , int key){
    int start = 0 , end = len - 1 , mid = 0;
    while(start <= end){
        mid = start + ((end - start) >> 1);
        if(array[mid] <= key){
            start = mid + 1 ;
        }else{
            end = mid - 1;
        }
    }
    return start < len ? start : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
    int mid = (start + end) >> 1;
    if(start > end){
        return start < len ? start : -1;
    }
    if(array[mid] > key){
        binSearchRecursion(array , start , mid - 1 , len , key);
    }else{
        binSearchRecursion(array , mid + 1 , end , len , key);
    }
}
4. 扩展版——查找有序数组中key元素位置的最小下标

查找有序数组中key元素位置的最小下标,没有key元素则返回-1。

解析:其实这个拓展版本和上面第二个基础版本基本一样的道理。唯一的区别是:上面的第二个基础版我们返回的第一个大于等于key的元素的下标值,即array[start] >= key;而这个拓展版要求我们找一个第一个等于key的元素下标值,即最小的start使得array[start] = key。所以,我们只需要在最后return的时候取判断一下返回的下标对应的值是不是等于key。其实,在第二个基础版中,如果数组中有key的话,返回的必定是key元素的最小下标,如果不存在key的话,返回的才是大于key的第一个元素,而前一种情况正是我们这个版本中需要求解的问题。

//非递归版本
int binSearch(int array[] , int len , int key){
    int start = 0 , end = len - 1 , mid = 0;
    int ans = -1;
    while(start <= end){
        mid = start + ((end - start) >> 1);
        if(array[mid] >= key){
            end = mid -1 ;
        }else {
            start = mid + 1;
        }
    }
    return ((start < len) && (array[start] == key)) ? start : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
    int mid = start + ((end - start) >> 1);
    if(start > end){
        return ((start <= len) && (array[start] == key)) ? start : -1;
    }
    if(array[mid] >= key){
        binSearchRecursion(array , start , mid - 1 , len , key);
    }else{
        binSearchRecursion(array , mid + 1 , end , len , key);
    }
}
5. 扩展版——查找有序数组中key元素位置的最大下标

查找有序数组中key元素位置的最大下标,没有key元素则返回-1。

解析:这个版本可以跟第三个基础版(查找有序数组中第一个大于key的下标)联系起来。当我们找到了有序数组中第一个大于key的下标,我们只需要在返回的时候去判断array[start - 1] == key。既然array[start]是第一个大于等于key的数,那么如果array[start - 1] == key,那么start - 1必定是我们要求的那个下标,否则不存在。这里还有一个地方需要注意就是start - 1 < 0, 所以,顺便判断一下start >= 1,这里不需要判断start <= len,因为start最多到len,这时候start - 1还是符合要求的。

//非递归版本
int binSearch(int array[] , int len , int key){
    int start = 0 , end = len - 1 , mid = 0;
    while(start <= end){
        mid = start + ((end - start) >> 1);
        if(array[mid] <= key){
            start = mid + 1;
        }else {
            end = mid -1 ;
        }
    }
    return ((start >= 1) && (array[start - 1] == key)) ? start - 1 : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
    int mid = start + ((end - start) >> 1);
    if(start > end){
        return ((start >= 1) && (array[start - 1] == key)) ? start - 1 : -1;
    }
    if(array[mid] > key){
        binSearchRecursion(array , start , mid - 1 , len , key);
    }else{
        binSearchRecursion(array , mid + 1 , end , len , key);
    }
}
6. 扩展版——查找有序数组中小于key元素的最大下标

查找有序数组中小于key元素的最大下标,没有key元素则返回-1。

解析:这个拓展版和第二个基础版(查找有序数组中第一个大于等于key的下标)可以联系起来。当我们找到了有序数组中第一个大于等于key的下标,那么array[start - 1] < keystart >= 1的时候必然是成立的。所以,和第二个基础版本的返回条件不同的是,我们需要判断一下start >= 1,这里不需要判断start <= len,因为start最多到len,这时候start - 1还是符合要求的。

//非递归版本
int binSearch(int array[] , int len , int key){
    int start = 0 , end = len - 1 , mid = 0;
    while(start <= end){
        mid = start + ((end - start) >> 1);
        if(array[mid] >= key){
            end = mid -1 ;
        }else {
            start = mid + 1;
        }
    }
    return (start >= 1) ? start - 1 : -1;
}
//递归版本
int binSearchRecursion(int array[] , int start , int end , int len , int key){
    int mid = start + ((end - start) >> 1);
    if(start > end){
        return (start >= 1) ? start - 1 : -1;
    }
    if(array[mid] >= key){
        binSearchRecursion(array , start , mid - 1 , len , key);
    }else{
        binSearchRecursion(array , mid + 1 , end , len , key);
    }
}
7. super版——查找有序数组中key元素出现的个数

查找有序数组中小于key元素出现的个数。

解析:这个就是第二个基础版和第三个基础版的组合,如果数组中存在key元素的话,第二个基础版(查找有序数组中第一个大于等于key的下标)就是找到第一个key元素,第三个基础版(查找有序数组中第一个大于key的下标)就是查找的最后一个key元素之后的一个值,两者返回值相减便是我们需要求的key元素的个数。如果数组中不存在key元素的话,第二个基础版(查找有序数组中第一个大于等于key的下标)和第三个基础版(查找有序数组中第一个大于key的下标)查找的结果是相同的,都是大于key元素的第一个值,两者返回值相减为0,表明有0个key元素。

//非递归版本
int binSearchMaxIndex(int array[] , int len , int key){
    int start = 0 , end = len - 1 , mid = 0;
    while(start <= end){
        mid = start + ((end - start) >> 1);
        if(array[mid] <= key){
            start = mid + 1 ;
        }else{
            end = mid - 1;
        }
    }
    return start < len ? start : -1;
}

int binSearchMinIndex(int array[] , int len , int key){
    int start = 0 , end = len - 1 , mid = 0;
    while(start <= end){
        mid = start + ((end - start) >> 1);
        if(array[mid] >= key){
            end = mid -1 ;
        }else {
            start = mid + 1;
        }
    }
    return start < len ? start : -1;
}
//递归版本
int binSearchRecursionMaxIndex(int array[] , int start , int end , int len , int key){
    int mid = (start + end) >> 1;
    if(start > end){
        return start < len ? start : -1;
    }
    if(array[mid] > key){
        binSearchRecursionMaxIndex(array , start , mid - 1 , len , key);
    }else{
        binSearchRecursionMaxIndex(array , mid + 1 , end , len , key);
    }
}

int binSearchRecursionMinIndex(int array[] , int start , int end , int len , int key){
    int mid = start + ((end - start) >> 1);
    if(start > end){
        return start < len ? start : -1;
    }
    if(array[mid] >= key){
        binSearchRecursionMinIndex(array , start , mid - 1 , len , key);
    }else{
        binSearchRecursionMinIndex(array , mid + 1 , end , len , key);
    }
}

PS:以上代码我已经在自己的机器上测试过,如果大家发现问题,欢迎指正~笔芯(•‾̑⌣‾̑•)✧˖°

点赞