LeetCode 筆記 (1) – Binary Search

LeetCode 筆記 (1) – Binary Search

簡介

Binary Search是十分常用的演算法,在許多程式語言中都是陣列或列表的標準函數。以下程式碼整理自OpenJDK :

int binarySearch(int[] a, int fromIndex, int toIndex, int key) {
    int low = fromIndex;
    int high = toIndex - 1;
    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = a[mid];
        if (midVal < key) // key在右邊
            low = mid + 1;
        else if (midVal > key) // key在左邊
            high = mid - 1;
        else // key在中間
            return mid; // key found
    }
    return -(low + 1);  // key not found.
}

除了學習演算法的運作原理之外,還要其他要點

  • 題目有時需要改變搜尋條件
  • 題目有時需要用到找不到key時的插入位置

解題範例

33. Search in Rotated Sorted Array

這一題很明顯是Binary Search的變形,只是差在陣列有被旋轉過,所以搜尋條件需要調整,先仿照本來的寫法再根據題意修改 :

public int search(int[] a, int key) {
    int n = a.length;
    if (n == 0) return -1; // 根據題意
    if (n == 1) return a[0] == key ? 0 : -1; // 根據題意
    
    int low = 0;
    int high = n - 1;
    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = a[mid];
        if (midVal < key) // 可能出錯
            low = mid + 1;
        else if (midVal > key) // 可能出錯
            high = mid - 1;
        else // 可能出錯
            return mid;
    }
    return -1; // 根據題意
}

當然這樣的程式是錯的,原因是已經排序好的陣列才適用Binary Search,而本題的陣列被破壞過一次,只有部分是有序的。因此我們需要在一邊執行演算法的同時,一邊檢查排序是否被破壞並進行修正。

而由於陣列的排序只被破壞一次,因此每次二分查找時,左右其中一邊一定是有序的,我們的目標會變成,只在確認目標值屬於有序的那一邊時,才按照本來的條件執行,否則就去找另一邊。注意左邊是小於等於中間值;右邊是大於中間值 :

public int search(int[] a, int key) {
    int n = a.length;
    if (n == 0) return -1;
    if (n == 1) return a[0] == key ? 0 : -1;
    
    int low = 0;
    int high = n - 1;
    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = a[mid];
        if (a[mid] == key) return mid; // 失去二分查找特性,因此在判斷哪邊
                                       // 有序之前,先檢查中間值
        if (a[low] <= a[mid]) // 左邊有序
            if (a[low] <= key && key < a[mid]) // 且key在左邊
                high = mid - 1; // 照舊
            else
                low = mid + 1; // 找另一邊
        else // 右邊有序
            if (a[mid] < key && key <= a[high]) // 且key在右邊
                low = mid + 1; // 照舊
            else
                high = mid - 1; // 找另一邊
    }
    return -1;
}

74. Search a 2D Matrix

本題更加單純,本質上只是把有序的陣列排成矩陣格式,因此只要在取值時轉換索引即可 :

public boolean searchMatrix(int[][] a, int key) {
    int h = a.length; // 取得矩陣大小
    int w = a[0].length;

    int n = h * w;
    if (n == 0) return false; // 根據題意
    if (n == 1) return a[0][0] == key; // 根據題意

    int low = 0;
    int high = n - 1;
    while (low <= high) {
        int mid = (low + high) >>> 1;
        int y = mid / w; // 計算mid索引的y軸
        int x = mid % w; // 計算mid索引的x軸
        int midVal = a[y][x]; // 取矩陣取得中間值
        if (midVal < key)
            low = mid + 1;
        else if (midVal > key)
            high = mid - 1;
        else
            return true;
    }
    return false;
}

81. Search in Rotated Sorted Array II

本題與33. Search in Rotated Sorted Array類似,差別在於陣列的內容並非嚴格遞增的,因此不能用二分法判斷左右哪一邊有序,所以需要先額外判斷無法判斷的狀況,並把左右翻犯縮減,然後再次搜尋 :

public boolean search(int[] a, int key) {
    int n = a.length;
    if (n == 0) return false; // 根據題意
    if (n == 1) return a[0] == key; // 根據題意

    int low = 0;
    int high = n - 1;
    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = a[mid];
        if (a[mid] == key) return true;
        if (a[low] == a[mid] && a[mid] == a[high]) { // 額外判斷
            ++low; // 無法判斷是否有序
            --high; // 只能左右減一縮小範圍
        } else {
            if (a[low] <= a[mid]) { // 左邊有序
                if (a[low] <= key && key < a[mid]) { // 且key在左邊
                    high = mid - 1; // 照舊
                } else {
                    low = mid + 1; // 找另一邊
                }
            } else {
                if (a[mid] < key && key <= a[high]) { // 且key在右邊
                    low = mid + 1; // 照舊
                } else {
                    high = mid - 1; // 找另一邊
                }
            }
        }
    }
    return false;
}

153. Find Minimum in Rotated Sorted Array

本題在二分查找時的目標從特定值變成最小值。當中間值小於右側值時表示可能為小值,且右邊不再需要搜尋。中間值大於右側值表示非最小值,且左側不再需要搜尋。

public int findMin(int[] a) {
    int low = 0;
    int high = a.length - 1;
    while (low < high) { // 由於high縮減時為mid,左右側不可能重和
        int mid = (low + high) >>> 1;
        if (a[mid] < a[high]) { // 最小值不再右右側
            high = mid; // 可能是最小值
        } else {
            low = mid + 1;
        }
    }
    return a[low];
}

162. Find Peak Element

本題與153. Find Minimum in Rotated Sorted Array類似,都是改變搜尋目標,假如右側數值較低的話,那封值就應該在左側,反之亦然 :

public int findPeakElement(int[] a) {
    int low = 0;
    int high = a.length - 1;
    while (low < high) {
        int mid = (low + high) >>> 1;
        if (a[mid + 1] < a[mid]) { // 右側較低
            high = mid; // 峰值在左側
        } else {
            low = mid + 1;
        }
    }
    return low;
}

One thought on “LeetCode 筆記 (1) – Binary Search

Leave a Reply

Your email address will not be published. Required fields are marked *