看了这篇帖子,对思路很简单,细节是魔鬼感同身受,故自己来做个总结。
1.普通的二分搜索
在一个一维有序数组 nums 中查找 target 是否存在,存在返回 index ,不存在返回 -1:
public int binarySearch(int[] nums, int target) {
//省去了特殊条件判断
int len = nums.length;
int left = 0;
int right = len - 1; //!!!
while (left <= right) { //!!!
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
需要注意的是 right 的赋值及 while 条件:我们设定 right = len - 1 时,二分搜索的区间为 [left,right] ,搜索循环终止的条件为:①找到 target ,即 nums[mid] == target ,则返回 mid;②当 left > right 时表明未找到 target,终止循环,返回-1。那么为什么循环条件是 left <= right:left < right 显然是必须的,我们再考虑临界情况,当 left + 1 = right 时进入循环,这时求得 mid = left,若 nums[mid] == target,程序直接返回 mid;若 nums[mid] < target,则需要右移 left,left = mid + 1 = left + 1 = right,此时 left = right ,搜索空间 [left,left](或 [right,right]) 并不为空,若循环条件中没有等于,则遗漏了该处元素的比较(当然也可以在循环外补充上此处比较);若 nums[mid] > target,则需要左移 right,right = mid - 1 = left - 1,此时 left > right,搜索空间 [left,right]为空,循环结束。
2.二分搜索左(右)边界
先来说明一下左边界和右边界,比如 nums = {1,2,2,2,2,3,4},那么当 target = 2 时,求左边界的返回值为 1(最左边的2),右边界的返回值为 4(最右边的2)。对于这种情况,基本的二分搜索返回的是其中的一个 2 而不是最左(右)边的。
先来看搜索左边界:
public static int binarySearchLeft(int[] nums, int target) {
int len = nums.length;
int left = 0;
int right = len;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target)
left = mid + 1;
else
right = mid;
}
return left;
}
该种情况下我们的搜索区间为 [left,right) ,所以赋值 right = len 且循环条件为 left < right,当 left = right 时跳出循环[left,left) 为空不会错过解。同样考虑当 left + 1 = right 时,mid = left,若 nums[mid] >= target,right = mid = left 循环结束;若nums[mid] < target,left = left + 1 = right 循环结束。可以看到最终循环结束,left 和 right 都指向数组中第一个大于等于 target 的元素。
右边界和左边界类似,当 nums[mid] == target 时,不要立即返回,而是增大「搜索区间」的下界 left,使得区间不断向右收缩,达到锁定右侧边界的目的:
public static int binarySearchLeft(int[] nums, int target) {
int len = nums.length;
int left = 0;
int right = len;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target)
left = mid + 1;
else if (nums[mid] < target)
left = mid + 1;
else
right = mid;
}
return left - 1;
}
同样分析 left + 1 = right 时,mid = left,若 nums[mid] >= target,left = mid + 1 = left + 1 = right 循环结束;若nums[mid] < target,right = mid 循环结束。可以看到最终循环结束,left-1 和 right-1 都指向数组中最后一个小于等于 target 的元素。
3.总结
与求边界的二分查找,最大的区别时搜索空间不同,所以定义左右标记也不同。
基本二分查找 | left = 0; right = len - 1; 循环条件:left <= right |
if (nums[mid] == target) return mid; else if (nums[mid] < target) left = mid + 1; else right = mid - 1; | |
左边界 | left = 0; right = len; 循环条件:left < right |
if (nums[mid] == target) right = mid; else if (nums[mid] < target) left = mid + 1; else right = mid; | |
右边界 | left = 0; right = len; 循环条件:left < right
|
if (nums[mid] == target) left = mid + 1; else if (nums[mid] < target) left = mid + 1; else right = mid; |
题目1:寻找 nums 中第一个 >= target 的数或者小于 target 的数有几个等情况。