二分查找的難點

二分查找有兩種實現(xiàn)方式:
遞歸實現(xiàn),循環(huán)迭代實現(xiàn),相比而言,迭代循環(huán)實現(xiàn)比較有難度

遞歸實現(xiàn)方式

public static int binarySearch(int[] param, int target) {

        if (param.length == 0) {
            return -1;
        }
        int middleIndex = param.length / 2;
        int middleValue = param[middleIndex];
        if (target == middleValue) {
            return target;
        }
        while (target != middleValue && target < middleValue) {
            int[] leftParam = Arrays.copyOfRange(param, 0, middleIndex);
            int result = binarySearch(leftParam, target);
            if (result == target) {
                return result;
            }else {
                return -1;
            }
        }
        while (target != middleValue && target > middleValue) {
            int[] rightParam = Arrays.copyOfRange(param, middleIndex + 1, param.length);
            int result = binarySearch(rightParam, target);
            if (result == target) {
                return result;
            }else {
                return -1;
            }
        }
        return -1;
    }

迭代實現(xiàn)方式

public static int binarySearch(int[] param, int target) {

        if (param.length == 0) {
            return -1;
        }
        int left = 0;
        int right = param.length-1;
        while (left<=right){
            int middle = left +(right-left)/2;
            if(target == param[middle]){
                return middle;
            }else if(target<param[middle]){
                right = middle-1;
            }else if(target>param[middle]){
                left = middle+1;
            }
        }
        return -1;
    }

二者的不同在于,遞歸方式每次切割一半的數(shù)組作為入?yún)?,從而縮小查找范圍
迭代方式改變索引搜索區(qū)間,每次左邊索引移動,或者右邊索引移動一個位置,縮小查詢的區(qū)間范圍

迭代法實現(xiàn)二分查找看似簡單,但細節(jié)很難把握,容易陷進坑里。到底是“<=” 還是“<” ,到底是arrays.length 還是arrays.length-1? 很容易搞暈。正如發(fā)明 KMP 算法的Knuth大佬說的:Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky...思維簡單很簡單,細節(jié)很磨人

下邊就抽絲剝繭,給你捋清楚:
1 while (left<=right) 處到底應該是left<=right還是left<right呢?首先要明白,<= 就等價于[1,2,3] 閉合區(qū)間,而< 就等價于[1,2,3)就是左閉右開的區(qū)間。所以首先搞清楚最后一位是否包閉合

2 right的值應該是param.length-1,還是param.length,決定了是否閉合 如果right=param.length 那么就應該是開區(qū)間,否則會索引越界,反之,如果right=param.length-1 那么while中就是閉區(qū)間,包含等號。即滿足這一條件的時候,還會進入while方法體,繼續(xù)執(zhí)行

3 為什么是left = mid+1; 因為while中的條件是閉合區(qū)間,所以本次搜索已經(jīng)驗證過mid位置處的值是否滿足,所以右半邊數(shù)組的起始索引就從下一位,即mid+1位置開始搜索。反之,如果搜索左半邊數(shù)組,就從上一位,mid-1位置,搜索左半邊位置,即right=mid-1;

4看完以后思路應該清晰了,但你會發(fā)現(xiàn)這個方法其實有很大的局限性,使用范圍有限。比如說給你有序數(shù)組 nums = [1,2,2,2,3],target = 2,此算法返回的索引是 2,沒錯。但是如果我想得到 target 的左側邊界,即索引 1,或者我想得到 target 的右側邊界,即索引 3,這樣的話此算法是無法處理的。

這樣的需求很常見。常見的思路可能會想,找到一個 target 索引,然后向左或向右線性搜索不行嗎?可以,但不是很好,脫離了二分查找的精髓了,因為這樣難以保證二分查找對數(shù)級的復雜度了。

接下來通過一個案例所展示的技巧解決這個問題,讓這個算法具有真正的實用價值。跟著我來一起學習吧。

下邊是尋找左邊界的代碼

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意

    while (left < right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;//原來這里會返回,但現(xiàn)在不返回,讓循環(huán)繼續(xù)
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;
}

1為什么 while(left < right) 而不是 <= ?
解答:這個問題應該不難回答了吧,因為right = nums.length 也就是索引越界的位置,所以應該是<左閉右開的區(qū)間形式,不包含最后一位
2 while(left < right) 終止的條件是 left == right,此時搜索區(qū)間 [left, left) 恰巧為空,所以可以正確終止,不再進入循環(huán)體中
3為什么沒有返回 -1 的操作?如果 nums 中不存在 target 這個值,怎么辦?這個需要一步步來,尤其要理解“左邊界”的特殊含義:


left_bound.png

nums數(shù)組中小于target=2的元素有1個
比如對于有序數(shù)組 nums = [3,4,5,6], target = 2,算法會返回 0,含義是:nums 中小于 2 的元素有 0 個。如果 target = 9,算法會返回 4,含義是:nums 中小于 8 的元素有 4 個

結合以上分析可以看出:返回的值(即left左邊界值)取值是閉區(qū)間[0,nums.length],所以我們簡單添加兩行代碼就能在正確的時候 return -1:

nums[left] == target? left:-1

4 left =mid+1,right=mid 這還是符合我們之前說的,左邊界右移,右邊界左移
5 為什么該算法能夠搜索左側邊界?
是下邊這段代碼

if (nums[mid] == target)
        right = mid;

這段代碼的意思就是將右邊界不斷替換為最新的mid值,而mid值是不斷減小的,在[left,mid) 位置上繼續(xù)搜索,這一點確實不容易想到,也是技巧所在了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容