二分查詢基礎
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二分基礎題目
思路解析:
通常一個整數的平方根是一個無限不迴圈小數,依題意知要返回一個小於等於平方根的整數。換句話說,我們就只需要從小於等於平方根的整數中找到最大的那個整數,符合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;
}
};
思路解析:
由題意知沒有重複的元素,故模板一、模板二都可以,這裡使用模板一。這裡要特殊考慮一下陣列為空,或者查詢元素小於、大於所有陣列元素的情況。
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;
}
};
思路解析:
這個思路很清晰,先使用模板一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;
}
};
思路解析:
單純的二分查詢,使用模板一、模板二都可以。關鍵在於二維矩陣下標的表示。
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;
}
};
思路解析:
由題意知,旋轉陣列如圖所示,要找最小值等價於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];
}
};
思路解析:
這道題是上一題的進階版,我們可以按照上一題的思路,先通過二分查詢找到旋轉陣列的最小值。然後對陣列最後一個數與目標數進行比較,縮小範圍。再進行一次二分查詢。
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;
}
};
思路解析:
這個一眼就能看出是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.總結
上面的題目也僅僅是刷一下二分查詢的熟練度,下次分享會總結下【二分答案】的專題,那個面試可能問的比較多。