相信大家对「二分搜索」都不陌生,比较高效的一个搜索策略。前提是,搜索的数组必须是有序的!!!
但是这个算法的麻烦之处在于「对边界的判断」。先看一个最基础的二分搜索模版
public int search(int[] nums, int target) {
int lo = 0;
int hi = nums.length - 1;
while (lo <= hi) {
int mid = lo - (lo - hi) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] < target) lo = mid + 1;
else if (nums[mid] > target) hi = mid - 1;
}
return -1;
}
这里面有几个需要注意的点:
[lo, hi]
lo = hi + 1
lo
和hi
都是不包括mid
的。原因:既然区间是全闭,所以收缩时,num[mid]
不符合条件下面有一个进阶版的二分搜索:寻找最左或最右的相等元素,如果不存在,返回 -1。详情可见题目 在排序数组中查找元素的第一个和最后一个位置
先给出模版,然后再进一步解释。先介绍寻找最左相等元素
xxxxxxxxxx
private int leftBound(int[] nums, int target) {
int lo = 0, hi = nums.length - 1;
while (lo <= hi) {
int mid = lo - (lo - hi) / 2;
// 向左收缩
if (nums[mid] == target) hi = mid - 1;
else if (nums[mid] < target) lo = mid + 1;
else if (nums[mid] > target) hi = mid - 1;
}
// 结束情况 : lo = hi + 1
// 未找到的情况
if (lo == nums.length || nums[lo] != target) return -1;
return lo;
}
和常规版的二分搜索不同之处:
寻找到相等元素,不是立刻返回,而是向左收缩区间
对于未找到元素的情况:(1) 可能比所有元素大;(2) 可能比所有元素小
[hi + 1, hi]
,例:[5, 4]
,其中num.length = 5
[lo, lo - 1]
,例:[0, -1]
对于第一种情况,我们通过lo == nums.length
判断;对于第二种情况,我们通过nums[lo] != target
判断
注意:必须第一种情况的判断写在前面,不然可能会出现越界
为什么对于找到元素的情况,最后返回lo
就是正确下标呢????
原因:首先,结束循环的条件为lo = hi + 1
;如果找到了相等元素,此时循环内的mid
就是正确下标;而由于收缩,hi = mid - 1
,刚好比正确下标少 1。所以lo
刚好是正确下标
下面介绍寻找最右相等元素的情况,其实和最左相似度很高
xxxxxxxxxx
private int rightBound(int[] nums, int target) {
int lo = 0, hi = nums.length - 1;
while (lo <= hi) {
int mid = lo - (lo - hi) / 2;
// 向右收缩
if (nums[mid] == target) lo = mid + 1;
else if (nums[mid] < target) lo = mid + 1;
else if (nums[mid] > target) hi = mid - 1;
}
// 结束情况 : hi = lo - 1
// 未找到的情况
if (hi < 0 || nums[hi] != target) return -1;
return hi;
}
对于未找到元素的情况:(1) 可能比所有元素大;(2) 可能比所有元素小
[hi + 1, hi]
,例:[5, 4]
,其中num.length = 5
[lo, lo - 1]
,例:[0, -1]
对于第二种情况,我们通过hi < 0
判断;对于第一种情况,我们通过nums[hi] != target
判断
注意:必须第二种情况的判断写在前面,不然可能会出现越界
为什么对于找到元素的情况,最后返回hi
就是正确下标呢????
原因:首先,结束循环的条件为hi = lo - 1
;如果找到了相等元素,此时循环内的mid
就是正确下标;而由于收缩,lo = mid + 1
,刚好比正确下标多 1。所以hi
刚好是正确下标
相信大家会有一个疑问,为什么「寻找最左相等元素」和「寻找最右相等元素」最后对于未找到的情况的判断方式有些区别呢???
前者通过lo
来判断,后者通过hi
来判断
这就要回到上面每种情况最后提出的那个问题了。对于「寻找最左相等元素」,lo
是正确下标,我们需要通过正确下标判断是否找到了目标元素;同理「寻找最右相等元素」也一样