二分查找能夠說是全部算法中最基礎、最容易理解的算法之一了,但事實上也是掛科率最高的考題之一,在各個大廠的應屆生面試中,這樣的評價家常便飯: 談項目的時候來聊的好好的,叫他寫個二分搜索卻寫不出來。對此我不作評論,就二分查找而言,我以爲它並無你們想象那樣容易,用「思路很簡單,細節是魔鬼」來形容最貼切不過了,不信我們來一步步瞧一瞧。面試
二分查找代碼大體以下:算法
let binarySearch = (arr, target) => {
let begin = 0;
let end = ???;
while(???) {
int mid = begin + (end -begin) / 2;
if(arr[mid] == target) {}
else if(arr[mid] > target) {}
else if(arr[mid] < target) {}
}
return ???;
}
複製代碼
能夠看到,問題已經很明顯了,不少人沒有準備的狀況下,基本的框架可以寫出來,可是邊界條件一直搞不清楚,致使情緒緊張、思惟錯亂,最後不了了之。框架
如今就來拆解一下各類邊界條件,spa
let search = (arr, target) => {
let begin = 0;
let end = arr.length-1; //寫成這樣,至關於搜索區間爲[begin, end],這是一個閉區間
while(begin <= end) {//重點: 由於閉區間,因此到了begin等於end時,其實區間內還有一個值要判斷,
//所以只有begin>end的時候才能中止
let mid = (begin + end) >>> 1;//位運算,無符號右移一位,同Math.floor((begin+end)/2)
if(arr[mid] == target) {
return mid;
}
else if(arr[mid] > target) {
end = mid - 1;//由於是閉區間,搜索範圍變爲[left, mid - 1]
}
else if(arr[mid] < target) {
begin = mid + 1; //搜索範圍變成[mid + 1, end]
}
}
return -1;
}
複製代碼
其實咱們也能夠這麼來作:3d
let search = (arr, target) => {
let begin = 0;
let end = arr.length; //寫成這樣,至關於搜索區間爲[begin, end),這是一個前閉後開的區間
while(begin < end) {//重點:
//由於前閉後開的區間,因此到了begin等於end時,其實區間內已經沒有值了,直接中止
let mid = (begin + end) >>> 1;
if(arr[mid] == target) {
return mid;
}
else if(arr[mid] > target) {
end = mid;//由於是閉區間,搜索範圍變爲[left, mid - 1]
}
else if(arr[mid] < target) {
begin = mid + 1; //搜索範圍變成[mid + 1, end]
}
}
return -1;
}
複製代碼
我想註釋已經解釋的足夠清楚了,但願你們可以理解區間判斷的原理,而不是去一味的背代碼。code
順便提一下,根據上面的思路,咱們也能夠寫成遞歸的方式:cdn
let search = (nums, target) => {
let helpSearch = (nums, begin, end, target) => {
if(begin > end) return -1;
let mid = (begin + end) >>> 1;
if(nums[mid] == target) return mid;
else if(nums[mid] > target)
return helpSearch(nums, begin, mid - 1, target);
else
return helpSearch(nums, mid+1, end, target);
}
//閉區間形式
return helpSearch(nums, 0, nums.length - 1, target);
}
複製代碼
let search = (nums, target) => {
let helpSearch = (nums, begin, end, target) => {
if(begin >= end) return -1;
let mid = (begin + end) >>> 1;
if(nums[mid] == target) return mid;
else if(nums[mid] > target)
return helpSearch(nums, begin, mid, target);
else
return helpSearch(nums, mid+1, end, target);
}
//前閉後開區間形式
return helpSearch(nums, 0, nums.length, target);
}
複製代碼
直接看一道真題吧:blog
O(log n)級別的時間複雜度,說白了就是二分查找,也就是說,咱們須要用二分查找的方式找到目標值最左邊的位置和最右邊的位置, 其實仍是有一些複雜的。遞歸
接下來讓咱們分步驟拆解。get
let left = 0;
let mid;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
right = mid; //重點
}
}
//分析:目前的nums[left]必定是大於等於target的值
if (left == nums.length) return -1;
else return nums[left] == target ? left : -1;
複製代碼
let left = 0;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
left = mid + 1; //注意
}
}
//分析: 目前的nums[left]左邊的部分都是大於等於target的部分,所以咱們取nums[left - 1]
if (left == 0) return -1;
else return nums[left - 1] == target ? left - 1: -1;
複製代碼
固然我這裏都是用的前閉後開的區間,你也能夠直接用閉區間完成。
var searchRange = function(nums, target) {
let left = 0;
let mid;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
right = mid;
}
}
let leftIndex = -1, rightIndex = -1;
if (left == nums.length) return [-1, -1];
else leftIndex = nums[left] == target ? left : -1;
left = 0; right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
left = mid + 1;
}
}
if (left == 0) return [-1, -1];
else rightIndex = nums[left - 1] == target ? left - 1: -1;
return [leftIndex, rightIndex];
};
複製代碼
總而言之,二分搜索思惟難度確實不大,可是要將邊界變量表明的區間含義理解的很是清楚,這樣纔不至於思惟紊亂,細節考慮清楚了,各類不一樣的方法即可以來去自如了。