二分查找算法

二分查找算法思想

1、数组要求是有序的

2、定义左右边界索引l、r,中间索引m=(l+r)/2

3、判断arr[m]与待查找值target的大小,不断减少右边界索引r或者增加左边界索引l

 

基础版二分查找

(1)如果target<arr[m],则证明待查找值在中间索引左侧,减少右索引r=m-1,继续下一轮查找

(2)如果如果target>arr[m],则证明待查找值在中间索引右侧,增加左索引l=m+1,继续下一轮查找

(3)如果如果target=arr[m],则证明找到待查找值,返回对应索引

(4)循环结束了l>r还没找到,即没有待查找值,返回-1

 

基础版代码:

public static int binarySearch(int [] arr,int target){
    int l = 0, r=arr.length-1;
    while (l<=r){
        int m = (l+r)>>>1;
        if(target<arr[m]){
            r=m-1;
        }else if(arr[m]<target){
            l=m+1;
        }else {
            return m;
        }

    }
    return -1;

}

 基础版二分查找算法性能分析:

1、最好情况:O(1),第一次循环的值就是待查找值

2、最坏情况:O(log(n))

 

平衡版二分查找

在上述基础版二分查找算法中,如果待查找元素在最左侧L次循环的位置,则需要执行L次,即每次只需要执行if(target<arr[m])分支,而待查找元素在最右侧L次的位置时候,则需要执行2L次,即每次既需要执行if(target<arr[m])分支,又需要执行if(arr[m]<target)。元素在左侧和右侧的执行效率不一样,在左侧时查找速率更快,在右侧时查找速率要慢点,平衡版二分查找可以让左侧右侧的效率都保持一样,下面给出左右查找效率一样平衡版二分查找的实现思路:

1、左闭右开的区间,左边界索引l可能是查找目标,而右边界索引r不可能是查找目标

2、不在循环内找出,等循环只剩下l时,退出循环,在循环外比较arr[l]与target

public static int binarySearch2(int [] arr,int target){
    int l = 0, r=arr.length;
    while (1 <r-l ){
        int m = (l+r)>>>1;
        if(target<arr[m]){
            r=m;
        }else {
            l=m;
        }

    }
    if(arr[l]==target){
        return l;
    }else {
        return -1;
    }
}

 平衡版二分查找中,无论待查找元素在最左侧L次还是在最右侧L次的位置,都是只需要执行if(target<arr[m])分支,执行次数都是L次,时间复杂度最好最坏情况都是O(log(n)),没有最好情况O(1),因为最好最坏情况都是执行完循环在循环外边判断是否找到值,可以认为是不快不慢算法,待查找元素在靠左侧还是在靠右侧查找效率一样,而上面的基础版二分查找待查找元素在靠左侧的查找效率要高一些

 

最左、右侧匹配的二分查找(LeftRightMost)

场景提出:上述的二分查找算法中,返回的元素不一定就是最左匹配的元素,下面给出最左侧匹配算法的思路:

1、定义一个候选者变量

2、如果找到待查找元素,则将m赋值到候选者,记录候选者位置

3、继续缩小查找范围,重复更新候选者位置

 

最左侧匹配的二分查找(LeftMost)

public static int binarySearchLeftMost(int [] arr,int target){
    int l = 0, r=arr.length-1;
    int candidate=-1;
    while (l<=r){
        int m = (l+r)>>>1;
        if(target<arr[m]){
            r=m-1;
        }else if(arr[m]<target){
            l=m+1;
        }else {
            //找到元素了,不直接返回m,而是记录候选者位置,继续缩小查找范围
           candidate = m;
           r = m-1;
        }

    }
    return candidate;
}

 

最左侧匹配的二分查找(LeftMost)-返回特定值

在上述算法,没有找到目标值时,返回的-1实际没什么意义,我们有时候希望在没找到元素时,可以返回≥target的最左侧索引

public static int binarySearchLeftMost2(int [] arr,int target){
    int l = 0, r=arr.length-1;
    while (l<=r){
        int m = (l+r)>>>1;
        if(target<=arr[m]){
            //无论target是否找到还是没找到,都继续缩小范围
            r=m-1;
        } else {
            l=m+1;
        }
    }
    //返回的l是≥target的最左侧索引
    return l;
}

 如测试用例1:

System.out.println(binarySearchLeftMost2(new int[]{1,2,5,5,5,8,8,8,10,22},7));

该结果会返回5,就是返回比待查找值'7'大的最左侧的那个值'8'的索引位置

测试用例2:

System.out.println(binarySearchLeftMost2(new int[]{1,2,5,5,5,8,8,8,10,22},5));

返回的是2,即找到待查找值'5'最左侧的的索引位置

最右侧匹配的二分查找(RightMost)

public static int binarySearchRightMost(int [] arr,int target){
    int l = 0, r=arr.length-1;
    int candidate=-1;
    while (l<=r){
        int m = (l+r)>>>1;
        if(target<arr[m]){
            r=m-1;
        }else if(arr[m]<target){
            l=m+1;
        }else {
            //找到元素了,不直接返回m,而是记录候选者位置,继续缩小查找范围
            candidate = m;
            l = m+1;
        }

    }
    return candidate;
}

 

最右侧匹配的二分查找(RightMost)-返回特定位置

public static int binarySearchRightMost2(int [] arr,int target){
    int l = 0, r=arr.length-1;
    while (l<=r){
        int m = (l+r)>>>1;
        if(target<arr[m]){
            r=m-1;
        }else {
            //无论target是否找到还是没找到,都继续缩小范围
            l=m+1;
        }

    }
    //返回的l-1是≤target的最右侧索引
    return l-1;
}

 如测试用例1:

System.out.println(binarySearchRightMost2(new int[]{1,2,5,5,5,8,10,22},7));

该结果返回的是:4,返回的是比待查找值'7'小的最靠右的那个元素'5'的索引位置

测试用例2:

System.out.println(binarySearchRightMost2(new int[]{1,2,5,5,5,8,8,8,10,22},8));

 该结果返回的是:7,即找到待查找值'8'最右侧的的索引位置

 

 

最左、右侧匹配查找的应用

名词解析:前任:<targert的最靠右侧元素(rightMost),后任:>target的最靠左侧元素(leftMost),最近邻居:前任后任看哪个位置举例target更近(target-rightMost索引的值与leftMost索引的值-target作比较取更小的)

 

力扣704题:

 

 

力扣35题:使用leftMost

 

 

posted @ 2024-03-26 14:30  筱筱创  阅读(17)  评论(0编辑  收藏  举报