一、二分查找
二分查找也是一种在数组中查找数据的算法。它只能查找已经排好序的数据。二分查找通过比较数组中间的数据与目标数据的大小,可以得知目标数据是在数组的左边还是右边。因此,比较一次就可以把查找范围缩小一半。重复执行该操作就可以找到目标数据,或得出目标数据不存在的结论。
例如:查找数字6
首先找到数组中间的数字,此处为5。
将5和要查找的数字6进行比较。
把不需要的数字移出查找范围。
在剩下的数组中找到中间的数字,此处为7。
比较7和6
把不需要的数字移出查找范围。
在剩下的数组中找到中间的数字,此处为6。
6=6,成功找到目标数字
解说:
**二分查找利用已排好序的数组,每一次查找都可以将查找范围减半。查找范围内只剩一个数据时查找结束。
**
时间复杂度:O(logn).
二、二分法的难点
1. 边界判断
二分法查找代码大致如下
function search (nums, target) {
var begin = 0;
var right = ???;
var middle = 0;
while(???) {
middle = Math.floor((left + right)/2);
if(nums[middle] === target) {}
else if(nums[middle] > target){}
else {}
}
return ???;
}
基本的框架已经出来了,难点在于边界条件不清楚。
边界问题一般有两种写法:
第一种写法:我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right]
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
-
while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
-
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
function search ( nums, target ) { ver left = 0; var right = nums.length - 1; var middle = 0; while(left <= middle) { // 因为left === right是有意义的,所以使用<= middle = Math.floor((left + right) / 2); if (nums[middle] === target) { return middle; } else if (nums[middle] > target) { right = middle - 1; // 因为是必区间,搜索范围为[left,middle-1] } else { left = middle + 1; // 因为是开区间,搜索范围为[middle + 1, right] } } return -1; }
第二种写法:定义了target在一个左闭右开的区间里,也就是 [left, right)
有如下两点:
-
while (left < right) 要使用 < ,因为left == right是有意义的,所以使用 <
-
if (nums[middle] > target) right 要赋值为 middle,因为当前这个nums[middle]一定不是target,那么在左侧区间查找,因为区间是左闭右开的区间,因此right = midle,那么接下来要查找的左区间结束下标位置就是 middlle
function search (nums,target) { var left = 0; var right = nums.length; var middle = 0; while(left < right) { middle = Math.floor((left + right) / 2); if(nums[middle] === target) { return middle; } else if (nums[middle] > target) { right = middle;// 因为是左闭右开区间,搜索范围是[left,middle) } else { left = middle+ 1;// 因为是左闭右开区间,搜索范围是[middle+1,right) } } return -1; }
2. 数据重复
统计一个数字在排序数组中出现的次数。
思路:找到这个数字在数组中的最大索引(右边界)rI,接着找这个数字在数组中的最小索引(左边界)lI,出现的次数为rI - II
1. 查找最大索引(右边界)bI
function serchRight(nums, target) {
let left = 0;
let right = nums.length -1;
let middle = 0;
while(left <= right) {
middle = Math.floor((left + right) / 2);
if(nums[middle] <= target) { // 只要中间值 <= target,那么最大索引在右区间
left = middle + 1;
} else { // 中间值 > target, 那么最大索引一定在左区间
right = middle - 1;
}
}
return right;// 也可以写成 left - 1
}
2. 查找完最大索引,需要判断这个数字是否在数组中
如果在数组中,则继续查找最小索引;如果不存在,直接返回0,没有查找最小索引的必要
function search(nums, target) {
var rI = searchRight(nums,target);
if (nums[rI] !== target) return 0;
// 查找最小左索引
}
3. 查找最小左索引
function searchLeft(nums, target) {
let left = 0;
let right = nums.length - 1;
let middle = 0;
while(left <= right) {
middle = Math.floor((left + right)/2);
if(nums[middle] >= target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return left;
}
4. 计算该数字出现的次数
function search(nums,target) {
let rI = searchRight(nums,target);
if (nums[rI] !== target) return 0;
let lI = searchLeft(nums,target);
return rI - lI + 1;
}