基礎二分查找
二分查找是大家最常用的也是最簡單的一種算法。二分查找在面試中是非常常見的一題,而且很多時候二分查找是時間效率最高的一種搜索方式。
最簡單的二分查找就是查找有序不重複數組中給定值的位置,基本模板如下:
var search = function(nums, target) {
var s = 0, e = nums.length - 1, m;
while(s+1<e) {
m = parseInt((s+e)/2);
if(nums[m] === target) {
return m;
} else if(nums[m] < target) {
s = m;
} else {
e = m;
}
}
if(nums[s] === target) return s;
if(nums[e] === target) return e;
return -1;
};
這裏一個技巧就是,爲了防止不小心陷入無限循環,當兩個指針相鄰的時候即結束循環,然後需要判斷下頭指針和尾指針的值是否滿足條件。
這個必須掌握,要寫出bug free的代碼,但是這個太簡單了,所以實際出題的時候肯定不會直接出這麼簡單題目。
進階二分查找
進階二分查找題目依然是在有序數組中找到一個給定值,不過會出現一些變化,這裏說幾個經典的題目。
第一種是搜索有重複的數組中第一個出現的或者最後一個出現的目標值的位置。
這裏直接給一個綜合的就是 Search for a range
,在有重複的排序數組中找到指定值出現的範圍,就要同時找到第一個出現的值和最後一個出現的值。
題目鏈接:https://leetcode.com/problems/search-for-a-range/
爲了方便,這裏我分成找第開頭和找結束兩步來進行:
var searchRange = function(nums, target) {
var result = [-1,-1];
//找開頭
var s = 0, e = nums.length-1, m;
while(s+1<e) {
m = Math.floor((s+e)/2);
if(nums[m] >= target) {
e = m;
} else {
s = m;
}
}
if(nums[s] === target) result[0] = s;
else if(nums[e] === target) result[0] = e;
else return result;
//找開頭
s = 0, e = nums.length-1, m;
while(s+1<e) {
m = Math.floor((s+e)/2);
if(nums[m] <= target) {
s = m;
} else {
e = m;
}
}
if(nums[e] === target) result[1] = e;
else if(nums[s] === target) result[1] = s;
return result;
};
第二種,比較簡單,叫 first bad version
,就是說在 1~n
個版本中有一個版本是錯誤的,然後給出了一個函數可以檢測指定的版本是否有錯誤。
題目鏈接 https://leetcode.com/problems/first-bad-version/
這種題目就是二分查找的方式幾乎沒有難度,但是會有稍微一點變化,比如這一題就是認識到通過 isBadVersion
函數來判斷保留左邊還是右邊。
解法如下:
var solution = function(isBadVersion) {
return function(n) {
var s = 1, e = n, m;
while(s+1<e) {
m = Math.floor((s+e)/2);
if(isBadVersion(m)) {
e = m;
} else {
s = m;
}
}
if(isBadVersion(s)) return s;
if(isBadVersion(e)) return e;
};
};
最後一個最難的個叫 Search in rotated sorted array
,就是數組從某一個位置被翻轉了一下,比如 [0,1,2,3,4,5]
被翻轉成了 [3,4,5,0,1,2]
當然題目是不會告訴我們從哪裏翻轉的。那麼我們有兩個解法,一種是先通過二分查找找到翻轉點,然後分別對左右兩個有序數組進行二分查找,另外一個就是隻用一次二分查找就可以解決,但是要注意移動指針的方式。
var search = function(nums, target) {
var s = 0, e = nums.length-1, m;
while(s+1<e) {
m = parseInt((s+e)/2);
if(nums[m] === target) return m;
else if(nums[m] < nums[s]) {
if(nums[m] <= target && target <= nums[e]) {
s = m;
} else {
e = m;
}
} else {
if(nums[m] >= target && target >= nums[s]) {
e = m;
} else {
s = m;
}
}
}
if(nums[s] === target) return s;
if(nums[e] === target) return e;
return -1;
};
解法如上, 這一題的要點就是確定指針應該如何移動,這裏我們會通過比較 nums[s],nums[m],nums[e],target
四個變量來確定如何移動指針,過程有點複雜但是基本原理就是確定target到底是在m的左邊還是右邊。
還有類似的比這個簡單一些,只要找最小值 https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/
另外還有 sqrt
,find peak
等題目。