Binary Search(一)
提起二分查詢,許多人都會想:這是一個簡單問題,我只用一個while或者遞迴就能解決,三行程式碼最多。再把上下限背過,就沒有什麼能難得住的二分查詢問題了。但事實究竟是這樣嗎? 我們一起從頭看一遍二分查詢,其中穿插各個OJ的二分查詢問題,同時時不時的我們會加入演算法導論中對於這個問題的引申。 ##什麼是二分查詢? 二進位制搜尋是電腦科學中最基本和最有用的演算法之一。 它描述了在有序集合中搜索特定值的過程。我們需要對二進位制搜尋中的名詞賦予一個簡單的定義:
target - 要搜尋的值 index - 搜尋的當前位置 left ,right - 我們用來維持搜尋空間的指標 mid- 用來應用條件來確定我們是應該向左還是向右搜尋的索引 ##二分查詢的過程 在二分查詢的基本形式中,二分搜尋在具有指定左右索引的連續序列(搜尋空間)上執行。維護搜尋空間的左,右和中間標記,並比較搜尋目標; 如果條件不滿足或者值不相等,則消除目標不可能存在的一半(有序序列),並繼續搜尋剩下的一半,直到成功為止。 如果搜尋以空的數字集合部分結束,則無法滿足條件並且未找到目標。 給定n個元素的排序(按升序排列)整數陣列nums和目標值,編寫一個函式來搜尋nums中的目標。 如果target存在,則返回其索引,否則返回-1
Leetcode Binary Search此程式碼只有 34.4% 為AC,甚至低於許多高階演算法!
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.size() == 0)return -1;
int left =0;
int right = nums.size()-1;
while(left<=right){
int mid = (left+right) >>1 ;
if(nums[mid]<target)left = mid+1;
else if(nums[mid]>target)right =mid-1;
else return mid;
}return -1;
}
};
演算法的應用
由於二進位制搜尋是一種演算法,在每次比較後將搜尋空間劃分為2。 所以每次需要搜尋集合中的索引或元素時,都應考慮二進位制搜尋。 如果集合是無序的,我們可以在應用二進位制搜尋之前對其進行排序1。
總的來說,我們可以將此部分分為:
Markdown畫圖,冒號後面必須空一格,不然報錯。我的天吶。對萌新一點都不友好。
我們總希望能夠研究的二分查詢問題越多越好,雖然我們擁有的思路相同,但每次我們檢視不同大佬程式碼時,它的實現似乎都略有不同。 儘管每個實現在每個步驟中將問題空間劃分為1/2,但其中一個有許多問題:
- 為什麼它的實現略有不同?
- 大佬在想什麼?
- 哪種方式更容易?
- 哪種方式更好?
###經過多次失敗的嘗試,和資料的閱讀查詢,附上幾個二分查詢的主要模板,並且附上幾個相似的例子。
1、同上面的基本演算法相同的寫法,我們的譚浩強、嚴蔚敏老師就是這麼教我們的
int binarySearch(vector<int>& nums, int target){
if(nums.size() == 0)
return -1;
int left = 0;
int right = nums.size() - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid - 1; }
}
return -1;
}
- 初始條件: left = 0, right = length-1(陣列終點下標)
- 終止條件: left > right
- 查詢左搜尋區間:right = mid -1
- 查詢右搜尋區間:left = mid+1
Ex1:計算並返回x的平方根,其中x保證為非負整數。
由於返回型別是整數,因此將截斷十進位制數字,並僅返回結果的整數部分。
迴圈體中的判斷條件
if(x/mid >= mid)
若為乘法if(mid*mid <=x)
時會**TLE
**,感興趣的話可以查閱相關資料。
class Solution {
public:
int mySqrt(int x) {
if (x <2)return x;
int left = 0;
int right = x ;
while(left< right){
int mid = (right + left) /2;
if (x/mid >= mid) left = mid+1;
else right = mid;
}return right-1;
}
};
當然我寫完了我的程式碼後也會出來看神仙:
class Solution {
public:
int mySqrt(int x) {
long r = x;
while (r*r > x)
r = (r + x/r) / 2;
return r;
嘖嘖嘖。由於此思路與本文主體無關,但既然看到了神仙,我們剛還在說要揣摩大佬的思路,所以特放在附錄裡2。
值得注意的是,上述方法當輸入為
但是一個題目的辦法多種多樣,除了上面的兩個,比如還有Shifting nth root algorithm
3
class Solution:
def mySqrt(self, x):
res = 0
bit = 1 << 30
while bit > x:
bit >>= 2
while bit != 0:
if x >= res + bit:
x -= res + bit
res += bit << 1
res >>= 1
bit >>= 2
return res
言歸正傳,我們能夠看到二分查詢用途廣且方便計算。現在我們來分析一下二分查詢的時間複雜度(討論非遞迴情況,遞迴方式見4)。 (原諒我遞迴方式 C語言版原諒我在wiki上直接粘了一個,就是這個↓)
int binary_search(const int arr[], int start, int end, int khey) {
if (start > end)
return -1;
int mid = start + (end - start) / 2; //直接平均可能會溢位,所以用此演算法
if (arr[mid] > khey)
return binary_search(arr, start, mid - 1, khey);
else if (arr[mid] < khey)
return binary_search(arr, mid + 1, end, khey);
else
return mid; //最後檢測相等是因為多數搜尋狀況不是大於要不就小於
}
易得 由主方法知 ,由於 考慮第二種情況:,則 5
Ex2:Guess Number Higher or Lower
我們正在玩猜數字遊戲。 遊戲如下: 我從1到n中選擇一個數字。 你猜測我選擇了哪個號碼。每次你猜錯了,我都會告訴你這個數字是高還是低。可呼叫預定義的API guess(int num),它返回3個可能的結果(-1,1或0)
class Solution {
public:
int guessNumber(int n) {
int left = 0;
int right = n;
if( n<2) return n;
while(left<=right){
int mid = left+(right-left)/2;//否則overflow會導致超時,其他需轉換。API傳入值為int。
if (guess(mid) == 0){
return mid;
}else if (guess(mid)==-1){
right = mid-1;
}
else left = mid +1;
}return -1;
}
};
沒有工作量,多調一個API而已。