基础二分查找
二分查找是大家最常用的也是最简单的一种算法。二分查找在面试中是非常常见的一题,而且很多时候二分查找是时间效率最高的一种搜索方式。
最简单的二分查找就是查找有序不重复数组中给定值的位置,基本模板如下:
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
等题目。