二分法的注意事項

二分法有多少種寫法都不重要,數組

重要的是要會寫一種對的。函數

 

首先有幾個數字要注意測試

中位數有兩個,spa

  1. 下位中位數:lowerMedian = (length - 2) / 2
  2. 上位中位數:upperMedian = length / 2

經常使用的是下位中位數,通用的寫法以下,語言int常常自動向下取整,翻譯

median = (length - 1) / 2

指針的區間固然能夠開區間,也能夠閉區間,也能夠半開半閉。但老老實實兩頭取閉區間老是不會錯。上面的中位數,轉換成兩頭閉區間 [low,high] 就變成下面這樣:指針

median = low + (high - low) / 2

這裏還有個常見的坑,不要圖快用加法,會溢出,code

median = ( low + high ) / 2 // OVERFLOW

 

另一個關鍵點是「終結條件」blog

不要以 low == high 作終結條件。會被跳過的,遞歸

if (low == high) { 
    return (nums[low] >= target)? low : ++low;
}

 

不相信在 [1, 5] 裏找 0 試試?ip

 

正確的終結條件是:

low > high

也就是搜索空間爲空。

 

知足終結條件之後,返回值徹底不須要糾結,直接返回低位 low

由於回過頭去放慢鏡頭,二分查找的過程就是一個 維護 low 的過程

low從0起始。只在中位數遇到肯定小於目標數時才前進,而且永不後退。low一直在朝着第一個目標數的位置在逼近。知道最終到達。

至於高位 high,就放心大膽地縮小目標數組的空間吧。

 

因此最後的代碼很是簡單,

public int binarySearch(int[] nums, int target) {
    int low = 0, high = nums.length-1;
    while (low <= high) { 
        int mid = low + (high - low) / 2;
        if (nums[mid] < target) { low = mid + 1; }
        if (nums[mid] > target) { high = mid - 1; }
        if (nums[mid] == target) { return mid; }
    }
    return low;
}

 

 

遞歸版也同樣簡單,

public int binarySearchRecur(int[] nums, int target, int low, int high) {
    if (low > high) { return low; } //base case
    int mid = low + (high - low) / 2;
    if (nums[mid] > target) {
        return binarySearchRecur(nums,target,low,mid-1);
    }  else if (nums[mid] < target) {
        return binarySearchRecur(nums,target,mid+1,high);
    } else {
        return mid;
    }
}

 

但上面的代碼能正常工做,有一個前提條件:

元素空間沒有重複值

推廣到有重複元素的空間,二分查找問題就變成:

尋找元素第一次出現的位置。

也能夠變相理解成另外一個問題,對應C++的 lower_bound() 函數

尋找第一個大於等於目標值的元素位置。

 

但只要掌握了上面說的二分查找的心法,代碼反而更簡單,

public int firstOccurrence(int[] nums, int target) {
    int low = 0, high = nums.length-1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (nums[mid] < target) { low = mid + 1; }
        if (nums[mid] >= target) { high = mid - 1; }
    }
    return low;
}

 

 

翻譯成遞歸版也是同樣,

public int firstOccurrenceRecur(int[] nums, int target, int low, int high) {
    if (low > high) { return low; }
    int mid = low + (high - low) / 2;
    if (nums[mid] < target) {
        return firstOccurrenceRecur(nums,target,mid + 1,high);
    } else {
        return firstOccurrenceRecur(nums,target,low,mid-1);
    }
}

 

以上代碼均經過leetcode測試。標準銀彈。天天早起寫一遍,鍛鍊肌肉。

 

最後想說,不要怕二分查找難寫,邊界狀況複雜。實際狀況是,你以爲煩躁,大牛也曾經由於這些煩躁過。一些臭名昭著的問題下面,常常是各類大牛的評論(噁心,變態,F***,等等)。並且這並不考驗什麼邏輯能力,只是仔細的推演罷了。拿個筆出來寫一寫,算一算不丟人。不少問題完全搞清楚之後,常常就是豁然開朗,而後之後妥妥觸類旁通。以上。

相關文章
相關標籤/搜索