二分查詢基礎專題——二分模板

程式設計異思坊發表於2021-11-03

二分查詢基礎

1.前言

以前總覺得得先把概念都過一遍完全理解了才能開始刷題,殊不知實踐才是掌握知識的捷徑,而不是背了忘忘了背。學知識本來就需要沉下心,一步一個腳印的走,否則在未來某個關鍵時刻會因當初的囫圇吞棗而受到懲罰。

所以別想那麼多,直接開刷就是了,先從簡單的二分查詢開始。但是有一說一,二分思想雖簡單,但是處理細節需格外小心,否則容易導致死迴圈。

2.概念核心

有序、折半查詢、時間複雜度O(logN)

3.演算法模板

通常我們的演算法題都不會直接讓我們用二分求某個數,都需要我們根據實際情況轉換一下。

二分題目通常分為兩種型別:

  • 第一種是查詢第一個滿足條件的下標,即00001111找第一個1的情況
  • 第二種是查詢最後一個滿足條件的下標,即11110000找最後一個1的情況
/*模板一(左邊為0右邊為1),查詢第一個1,00001111
* check(mid)意思為找到'1',此時mid可能是右邊任何一個'1',
* 即無法確定mid是否為第一個'1',所以我們在向左邊縮小範圍時不能寫成
* r=mid-1,要寫成r=mid;
* 同理,else情況為沒找到'1',所以向右邊縮小範圍,因為mid肯定不滿足條件,所以l=mid+1
*/

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;  
        else l = mid + 1;
    }
    return l;
}
/*模板二(左邊為1右邊為0),查詢最後一個1,11110000
* check(mid)意思為找到'1',此時mid可能是左邊任何一個'1',
* 即無法確定mid是否為最後一個'1',所以我們在向右邊縮小範圍時不能寫成
* l=mid+1,要寫成l=mid;
* 同理,else情況為沒找到'1',所以向左邊縮小範圍,因為mid肯定不滿足條件,所以r=mid-1
*/

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;	//加1避免死迴圈 eg:10
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

4.Leetcode二分基礎題目

69.Sqrt(x)

思路解析:
通常一個整數的平方根是一個無限不迴圈小數,依題意知要返回一個小於等於平方根的整數。換句話說,我們就只需要從小於等於平方根的整數中找到最大的那個整數,符合11110000找最後一個1的模型,即模板二。
最後注意下資料範圍即可。

class Solution {
public:
    int mySqrt(int x) {
        long long l = 0, r = x;
        while (l < r) {
            long long mid = l + r + 1 >> 1;
            if (mid * mid <= (long long)x) l = mid;
            else r = mid - 1;
        }
        return l;
    }
};

35.搜尋插入位置

思路解析:
由題意知沒有重複的元素,故模板一、模板二都可以,這裡使用模板一。這裡要特殊考慮一下陣列為空,或者查詢元素小於、大於所有陣列元素的情況。

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int len = nums.size();
        if (len == 0) return 0;
        if (nums[len - 1] < target) return len;
        if (nums[0] > target) return 0;
        int l = 0, r = len - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        return l;
    }
};

34.在排序陣列中查詢元素的第一個和最後一個位置

思路解析:
這個思路很清晰,先使用模板一00001111查詢第一個1;然後從找到的第一個1開始使用模板二11110000查詢最後一個1

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int len = nums.size();
        if (len == 0) return {-1, -1};
        vector<int> vec;
        int l = 0, r = len - 1, st = 0, ed = 0;
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }

        if (nums[l] == target) {
            st = l;
            int low = st, high = len - 1;
            while (low < high) {
                int mid = low + high + 1 >> 1;
                if (nums[mid] > target) high = mid - 1;
                else low = mid;
            }
            ed = low;
            vec.push_back(st);
            vec.push_back(ed);
        } else {
            vec.push_back(-1);
            vec.push_back(-1);
        }
        return vec;
    }
};

74.搜尋二維矩陣

思路解析:
單純的二分查詢,使用模板一、模板二都可以。關鍵在於二維矩陣下標的表示。

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if(matrix.size() == 0 || matrix[0].size() == 0) return false;
        int n = matrix.size();
        int m = matrix[0].size();
        int l = 0 ,r = n * m - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(matrix[mid/m][mid%m] >= target) r = mid;
            else l = mid + 1;
        }
        if(matrix[r/m][r%m] == target) return true;
        return false;
    }
};

153.尋找旋轉排序陣列中的最小值

思路解析:
由題意知,旋轉陣列如圖所示,要找最小值等價於00001111找第一個1。我們只要把判斷條件設定為小於等於陣列最後一個值,此時左邊由圖知是大於陣列最後一個值的,設定為0,右邊設定為1

class Solution {
public:
    int findMin(vector<int>& nums) {
        int len = nums.size();
        int l = 0, r = len - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] <= nums.back()) r = mid;
            else l = mid + 1;
        }
        return nums[l];
    }
};

33.搜尋選擇排序陣列

思路解析:
這道題是上一題的進階版,我們可以按照上一題的思路,先通過二分查詢找到旋轉陣列的最小值。然後對陣列最後一個數與目標數進行比較,縮小範圍。再進行一次二分查詢。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int len = nums.size();
        if (len == 0) return -1;
        int l = 0, r = len - 1;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (nums[mid] <= nums.back()) r = mid;
            else l = mid + 1;
        }
        if (target <= nums.back()) r = len - 1;
        else l = 0, r--;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        return nums[l] == target ? l : -1;      
    }
};

278.第一個錯誤的版本

思路解析:
這個一眼就能看出是00001111查詢第一個1的模型,套模板就可以。

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        int l = 1, r = n;
        while (l < r) {
            int mid = l + (r - l >> 1);
            if (isBadVersion(mid)) r = mid;
            else l = mid + 1;
        }
        return l;
    }
};

5.總結

上面的題目也僅僅是刷一下二分查詢的熟練度,下次分享會總結下【二分答案】的專題,那個面試可能問的比較多。

原文地址

相關文章