二分查找

基礎

基本寫法(元素沒有重複)

int binarySearch(vector<int> &A, int target) {
    // [left, right]
    int left = 0, right = (int)A.size() - 1;
    while (left <= right) {
        int mid = left + (right - left)/2;
        if (A[mid] == target) {
            return mid;
        } else if(A[mid] > target) {
            right = mid - 1;
        } else if (A[mid] < target) {
            left = mid + 1;
        }
    }
    return -1;
}

// 遞歸寫法
int binarySearchRecur(vector<int> &A, int target, int left, int right) {
    if (left > right) { return -1; }
    int mid = left + (right - left)/2;
    if (A[mid] > target) {
        return binarySearchRecur(A, target, left, mid-1);
    } else if (A[mid] < target) {
        return binarySearchRecur(A, target, mid + 1, right);
    } else {
        return mid;
    }
}

複製代碼

查找左邊界

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

int searchLeftBound(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(target == nums[mid]) {
                right = mid - 1;
            } else if(target < nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        if(left < nums.size() && nums[left] == target) {
            return left;
        } 
        return -1;
    }
複製代碼

尋找左側邊界,小於目標元素git

int binarySearchLeftBound(vector<int> &A, int target) {
    // [left, right]
    int left = 0, right = (int)A.size() - 1;
    while (left <= right) {
        int mid = left + (right - left)/2;
        if (A[mid] == target) {
            right = mid - 1;
        } else if (A[mid] > target) {
            right = mid - 1;
        } else if (A[mid] < target) {
            left = mid + 1;
        }
    }
    return left - 1;
}
複製代碼

查找右邊界

尋找元素最後一次出現的位置github

int searchRightBound(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(target == nums[mid]) {
            left = mid + 1;
        } else if(target < nums[mid]) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    if(right >= 0 && nums[right] == target) {
        return right;
    }
    return -1;
}
複製代碼

尋找右側邊界,大於目標元素數組

int binarySearchRightBound(vector<int> &A, int target) {
    // [left, right]
    int left = 0, right = (int)A.size() - 1;
    while (left <= right) {
        int mid = left + (right - left)/2;
        if (A[mid] == target) {
            //[mid, right]
            left = mid + 1;
        } else if (A[mid] > target) {
            right = mid - 1;
        } else if (A[mid] < target) {
            left = mid + 1;
        }
    }
    return left;
}
複製代碼

LeetCode 題型

easy 題型

704. 二分查找markdown

374. 猜數字大小oop

278. 第一個錯誤的版本ui

medium 題型

34. 在排序數組中查找元素的第一個和最後一個位置(查找目標值的左右邊界)spa

最直接的想法是先找左邊界,在找右邊界:code

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left = searchLeftBound(nums, target);
        int right = searchRightBound(nums, target);
        vector<int> v;
        v.push_back(left);v.push_back(right);
        return v;
    }

    int searchLeftBound(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(target == nums[mid]) {
                right = mid - 1;
            } else if(target < nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        if(left < nums.size() && nums[left] == target) {
            return left;
        } 
        return -1;
    }
    int searchRightBound(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(target == nums[mid]) {
                left = mid + 1;
            } else if(target < nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        if(right >= 0 && nums[right] == target) {
            return right;
        }
        return -1;
    }
};
複製代碼

658. 找到 K 個最接近的元素(題目有點繞)orm

旋轉問題

33. 搜索旋轉排序數組(沒有重複元素,找目標值)

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] >= nums[left]) {
                // 說明在 ...left...mid... [left, mid] 升序
                if(target == nums[mid]) {
                    return mid;
                } else if(target >= nums[left] && target < nums[mid]) {
                    // ...left...target...mid...
                    right = mid - 1;
                } else {
                    // target 可能 left...mid...target?...
                    left = mid + 1;
                }
            } else {
                // 說明 [mid, right] 是升序的
                if(target == nums[mid]) {
                    return mid;
                } else if(target > nums[mid] && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return -1;
    }
};
複製代碼

解題思路是要肯定 target 在哪一個區間,剩下的就好辦了,按照二分搜索的模板寫就好了。

可是要注意 nums[mid] >= nums[left] 這個判斷條件如:[3, 1]

81. 搜索旋轉排序數組 II(有重複的元素,找目標值)

class Solution {
public:
    bool search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while(left <= right) {
            int mid = left + (right - left)/2;
            if(nums[mid] > nums[left]) {
                if(target == nums[mid]) {
                    return true;
                } else if(target >= nums[left] && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else if(nums[mid] == nums[left]) {
                if(target == nums[left]) {
                    return true;
                } else {
                    left = left + 1;
                }
            } else {
                if(target == nums[mid]) {
                    return true;
                } else if(target > nums[mid] && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return false;
    }
};
複製代碼

和沒有重複的同樣思路,可是要處理重複的元素,因此單拎出來處理,由於不知道具體的狀況,故只能一個一個的來。

153. 尋找旋轉排序數組中的最小值(無重複元素,找最小值)

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] >= nums[left]) {
                if(nums[left] > nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            } else {
                if(nums[left] > nums[right]) {
                    left = left + 1;
                } else {
                    right = mid - 1;
                }   
            }
        }
        return nums[left];
    }
};
複製代碼

這個和旋轉數組的寫法同樣,肯定上升的和降低的區間,而後不斷縮小空間

另外一種寫法是考慮右邊,由於旋轉以後右邊的小的可能性較大,並且要考慮的條件也比較少

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while(left < right) {// left == mid == right
            int mid = left + (right - left) / 2;
            if(nums[mid] < nums[right]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return nums[left];
    }
};
複製代碼

154. 尋找旋轉排序數組中的最小值 II(有重複值,找最小值)

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while(left < right) {// left == mid == right
            int mid = left + (right - left) / 2;
            if(nums[mid] < nums[right]) {
                right = mid;
            } else if(nums[mid] == nums[right]) {
                right = right - 1;
            } else {
                left = mid + 1;
            }
        }
        return nums[left];
    }
};
複製代碼

這個和沒有重複值的思路同樣,nums[mid] == nums[right] 的時候要特殊處理以下面的特殊用例:

// mid 在坐邊
[2,2,2,2,2,2,2,2,2,0,2,2]
// mid 在右邊
[2,2,0,2,2,2,2,2,2,2,2,2]
// 由於不能肯定這兩種狀況,因此單拎出來處理,逐步向最小值靠攏
複製代碼

峯值問題

這個問題是旋轉數組的延續

162. 尋找峯值

二分

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int left = 1, right = arr.size() - 1;// [1, n - 1]
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(arr[mid - 1] < arr[mid]) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
};
複製代碼

往常咱們使用「二分」進行查值,須要確保序列自己知足「二段性」:當選定一個端點(基準值)後,結合「一段知足 & 另外一段不知足」的特性來實現「折半」的查找效果。

但本題求的是峯頂索引值,若是咱們選定數組頭部或者尾部元素,其實沒法根據大小關係「直接」將數組分紅兩段。

但能夠利用題目發現以下性質:因爲 arr 數值各不相同,所以峯頂元素左側必然知足嚴格單調遞增,峯頂元素右側必然不知足。

所以 以峯頂元素爲分割點的 arr 數組,根據與 前一元素/後一元素 的大小關係,具備二段性:

  • 峯頂元素左側知足 arr[i-1] < arr[i]arr[i−1]<arr[i] 性質,右側不知足
  • 峯頂元素右側知足 arr[i] > arr[i+1]arr[i]>arr[i+1] 性質,左側不知足

注意:計算 mid 的時候要向上取整,不然會死循環

三分

事實上,咱們還能夠利用「三分」來解決這個問題。

顧名思義,「三分」就是使用兩個端點將區間分紅三份,而後經過每次否決三分之一的區間來逼近目標值。

具體的,因爲峯頂元素爲全局最大值,所以咱們能夠每次將當前區間分爲 [l, m1] 、**[m1,m2] ** 和 **[m2, r]**三段,若是知足 arr[m1] > arr[m2],說明峯頂元素不可能存在與 [m2, r] 中,讓 r = m2 - 1 便可。另一個區間分析同理。

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int left = 0, right = arr.size() - 1;// [1, n - 1]
        while(left < right) {
            int m1 = left + (right - left) / 3;
            int m2 = right - (right - left) / 3;
            if(arr[m1] > arr[m2]) {
                right = m2 - 1;
            } else {
                left = m1 + 1;
            }
        }
        return left;
    }
};
複製代碼

852. 山脈數組的峯頂索引

參考

刷題路線

相關文章
相關標籤/搜索