二分查找的前提:有序。
这些序信息能够确保你可以大胆的缩减空间,进行定位。
二分查找的两个关键点:
1.缩减规则的制定。
我们接触的二分查找很多是从有序数组中查找某个数,所以缩减规则比较单一。(大于目标值,往哪边走,小于往哪边走?)
但实际中还有很多复杂的场景,比如说寻找最近的k个数的问题。
【例子】在有序数组中二分查找某个数
(1)【缩减规则】 目标值与中间值的比较。(等于,大于,或者小于将导致不同的变化)
(2)这规则是容易看出来的。
public static int binarySearch(Integer[] srcArray, int des) {
//定义初始最小、最大索引
int low = 0;
int high = srcArray.length - 1;
//确保不会出现重复查找,越界
while ((low <= high) && (low <= srcArray.length - 1)
&& (high <= srcArray.length - 1)) {
//计算出中间索引值
int middle = (high + low)/2 ;
if (des == srcArray[middle]) {
return middle;
//判断下限
} else if (des < srcArray[middle]) {
high = middle - 1;
//判断上限
} else {
low = middle + 1;
}
}
//若没有,则返回-1
return -1;
}
【例子】寻找最近的K个数
题目参考:【疑问】leetcode - 658. Find K Closest Elements【二分查找 + 双指针 + 找最近】
解法:(二分查找)
【目标】本题的目标在于,在有序数组中寻找离 x 最近的k个数。
【规则观察】经过仔细观察,我们可以发现在k个相邻的元素中,最远的情况出现在该K个元素的最左端( kLleft )或者最右端( kRight )。接下来让我们来进行一下简单的推理:
我们此处认为,x一定是在区间的中间的部分,才能最近。
(1) 假如说 x−kLeft>kRight−x ,那么会导致 x−i>kRight−x ,其中 i<kLeft 。
而
j−x>kRight−x,其中j>kRight。
因为x−kLeft>kRight−x
j−x存在潜在的可能性小于x−kLeft
也就是说,将k个区间右移,可能导致总体距离变小(kRight+1−x替换了x−kLeft)【规则制定】规则在于确定k个区间的起点。
(1) x - arr.get(mid) > arr.get(mid + k) - x 导致 右移1个单位 start = mid + 1
(2)否则,左移一个单位
class Solution {
public List<Integer> findClosestElements(List<Integer> arr, int k, int x) {
int start = 0, end = arr.size() - k;
while(start < end) {
int mid = start + (end - start) / 2;
if(x - arr.get(mid) > arr.get(mid + k) - x) {
start = mid + 1; //起点在mid右边。(排除mid,因为mid到mid + k区间中有 k + 1 个元素)
}else {
end = mid; //起点在mid左边。(包括mid)
}
}
return arr.subList(start, start + k);
}
}
2.边界控制
边界控制的细节经常让人感到头疼。下面分析一下,如何去理解这些边界。
我们此处设置几个变量来进行说明:
start:要查询区间的左端点。
end:要查询区间的右端点。
mid:区间靠近中间的点。(start +end)/ 2
target:待查找值。
(区间数值按从小到大排序)
接下来我们开始分析,
mid 大于 target
根据有序信息,以及有序的传递性,我们可以知道mid右边的区间的所有数值也大于target,所以我们相当于对mid及其右边的区间都已经进行了比较。(换句话说,右边区间数字和target的关系(大于关系)等同于mid与target的关系)
所以mid及其右边的区间已经没有了寻找的价值,将区间缩小,得到右端点 end = mid-1。
当mid小于target时,也可以做类似的分析。( start = mid +1 )
……
区间缩减
区间就这样一步一步被缩减了。
【缩减后的效果】前面的缩减规则确定了,每一步一定会将不符合要求的区间排除出去,但是不确定剩下有没有目标。(剩下的有待进一步探索,类似于做题时候的排除法)
再次重申一下观点,此处认为缩减的过程,是在排除不符合要求的内容,因为那些真正比较过了(可确定为无用)。
那么最后区间会缩减成什么状态呢?
这里考虑3种情况,
start = end ,区间有一个元素。
start +1 =end,区间有两个元素。
start + 2 =end,区间有3个元素。(加入该种情况是为方便理解)所有的缩减区间最后都会变为这3种情况的一种,(接下来考虑找不到目标值的情况)
情况3经过一步会变成情况1。(一次比较,能够消去两元素)。
情况2下一个状态有两种,其一是变为第一种情况,其二是终止。(这里说明了为什么不能将start == end设为终止条件,这种情况会将其跳过,直接从 start +1=end,变为start > end,因为同时排除了两个元素)
情况1经过一步比较,一定可以排除1个元素,导致start 大于end.