看看你是否真正掌握了Binary Search

二分查找(Binary Search)是数据结构与算法课程中应该讲到的一个非常重要的内容。通常,二分查找算法可以借由递归的结构来实现,也可以通过迭代来实现。但是在实际应用中二分查找可能变化出许多更为复杂的形式,面对这些变种题目时能否得心应手地来处理,便考察了你是否真正掌握了二分查找的精髓。


关于二分查找(Binary Search)的基本内容,我们不打算在此赘述,如果你对这个问题本身不甚了解,也可以参考《算法之美——隐匿在数据结构背后的原理(C++版)》一书163页对应之内容。而本文主要讨论几个常见的二分查找变种题目。


言归正传,首先来看一道Leetcode题目(该题目的难道等级为Medium):

《看看你是否真正掌握了Binary Search》

显然,这道题目的意思是在说给定一个有序(非降序)数组 nums 和一个 target,如果target在数组中出现,返回位置,若不存在,返回它应该插入的位置注意不能直接采用线性方法,本文中所有的题目都要使用Binary Search的方法来解决。

这里给出一个非递归的版本:

int searchInsert(int* nums, int numsSize, int target) {
    int low=0, high=numsSize-1;
    if(target > *(nums+high))  return high+1;  
    if(target < *(nums+ low))  return low; 
    
    while(low<=high)
    {
        int mid=(low+high)/2;
        
        if(nums[mid]<target)
            low =mid+1;    
        if(nums[mid]>target)    
            high=mid-1;
        if(nums[mid]==target)    
            return mid; 
    }
    return low;
}

下面我们把上述问题扩展,来看几个变种题目。

1、给定一个有序(非降序)数组 nums,可含有重复元素,求最小的下标 i 使得nums[i]等于target,不存在则返回-1

就问题本身而言,这显然是要求target在数组中第一次出现的位置。注意,直接使用原始的二分查找并不可行。

#include <stdio.h>

int searchFirstPos(int* nums, int numsSize, int target)
{
    if(numsSize <= 0) return -1;
    int low = 0, high = numsSize-1;
    
    while(low < high)
    {
        int mid = low +((high-low)>>1);
        
        if( *(nums+mid) < target)
            low = mid+1;
        else // nums[mid] >= target
            high = mid;
    }

    if(*(nums+low) != target)
        return -1;
    else
        return low;
}

int main(int argc, const char * argv[]) {
    
    int array[9] = {0, 1, 2, 3, 3, 3, 4, 5, 6};
    printf("%d\n", searchFirstPos(array, 9, 3));
    return 0;
}

2、给定一个有序(非降序)数组nums,可含有重复元素,求最大的下标 i 使得nums[i]等于target,不存在则返回-1。

此题是求target在数组中最后一次出现的位置。处理思路与上一题基本一样,但是有个tricky是地方需要注意。主要是计算中间位置时不能用low+((high-low)>>1),因为当low+1等于high且nums[low] <= target时,会导致死循环;所以这里要使用low+((high-low+1)>>1),这样能够保证循环会正常结束。

#include <stdio.h>

int searchLastPos(int* nums, int numsSize, int target)
{
    if(numsSize <= 0) return -1;
    int low = 0, high = numsSize-1;
    
    while(low < high)
    {
        int mid = low+((high-low+1)>>1);
        
        if(nums[mid] > target)
            high = mid-1;
        else // nums[mid] <= target
            low = mid;
    }

    if(*(nums+high) != target)
        return -1;
    else
        return high;
}


int main(int argc, const char * argv[]) {
    
    int array[9] = {0, 1, 2, 3, 3, 3, 4, 5, 6};
    printf("Last Position is %d\n", searchLastPos(array, 9, 3));
    return 0;
}

3、给定一个有序(非降序)数组nums,可含有重复元素,求target在数组中出现的次数。

当然我们不能去一个一个数target出现了多少次,因为这样的复杂度是O(n)。如果要采样二分查找的方法,可以求出第一次出现位置和最后一次出现位置,然后再做差即可。由于searchFirstPos()和前面都已实现,这里不多解释。


#include <stdio.h>

int count(int nums[], int numsSize, int target)
{
    int firstPos = searchFirstPos(nums, numsSize, target);
    if(firstPos == -1)
        return 0;
    int lastPos = searchLastPos(nums, numsSize, target);
    return lastPos-firstPos+1;
}

int main(int argc, const char * argv[]) {
    
    int array[9] = {0, 1, 2, 3, 3, 3, 4, 5, 6};
    printf("Appears %d times.\n", count(array, 9, 3));
    return 0;
}

4、给定一个有序(非降序)数组nums,可含有重复元素,求最小的下标i使得nums[i]大于target,不存在则返回-1。


显然,题目是要求大于target的最小元素的位置。循环过程中,当low大于0时,nums[low-1]是小于等于target的,因为nums[mid] <= target时,low=mid+1;当high小于n-1时,nums[high]是大于target的,因为nums[mid] > target时,high = mid;循环结束时,low 等于 high,所以,如果nums[high] 或 nums[low] 大于target, 那么high=low就是要找的位置,否则不存在这样的位置。


#include <stdio.h>

int searchFirstPosMoreThan(int *nums, int numsSize, int target)
{
    if(numsSize <= 0) return -1;
    int low = 0, high = numsSize-1;
    
    while(low < high)
    {
        int mid = low+((high-low)>>1);
        if( *(nums+mid) > target)
            high = mid;
        else // nums[mid] <= target
            low = mid+1;
    }
   
    return nums[high] > target ? high : -1;
}

int main(int argc, const char * argv[])
{
    int array[9] = {0, 1, 2, 3, 3, 3, 4, 5, 6};
    printf("%d\n", searchFirstPosMoreThan(array, 9, 3));
    printf("%d\n", searchFirstPosMoreThan(array, 9, 6));
    return 0;
}

5、
定一个有序(非降序)数组nums,可含有重复元素,求最大的下标i使得nums[i]小于target,不存在则返回-1。

与之前的求解思路类似,这里不再赘述。

#include <stdio.h>

int searchLastPosLessThan(int *nums, int numsSize, int target)
{
    if(numsSize <= 0) return -1;
    int low = 0, high = numsSize-1;
    
    while(low < high)
    {
        int mid = low+((high-low+1)>>1);
        if(*(nums+mid) < target)
            low = mid;
        else // num[mid] >= target
            high = mid-1;
    }

    return nums[low] < target ? low : -1;
}

int main(int argc, const char * argv[]) {
    
    int array[9] = {0, 1, 2, 3, 3, 3, 4, 5, 6};
    printf("%d\n", searchLastPosLessThan(array, 9, 4));
    printf("%d\n", searchLastPosLessThan(array, 9, -1));
    return 0;
}

(全文完)

    原文作者:算法
    原文地址: https://blog.csdn.net/baimafujinji/article/details/6477055
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞