二分查找
第一次刷LC,做点小总结,有点小激动
0. 基本
1.求中间mid索引时应
int mid = left + (right-left)/2;
而不是
int mid = (left+right)/2;
可以防止left+right溢出(超出整数范围)
2.不是二分就一定要像排序一样递归的
对于 简单的查找操作,循环即可解决
3.重点: mid 加一还是减一,while 里到底用 <= 还是 <
4.基本框架:
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
1. 简单的查找 704
int left = 0 , right = numsSize-1 , mid;
while (left <= right)
{
mid = left + (right - left)/2;
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid +1;
else if (nums[mid] > target)
right = mid -1;
}
return -1;
关键点:查找范围
right = numsSize-1 表明查找范围是[l,r] 而不是[l,r)
自然可以允许 l == r,即一个元素的处理
2. 第一个错误的版本 278
找左F右T值的边界
第一次尝试:
查找一个左邻元素是F的T
问题:
- 第一个元素没有左邻
- 此时用的是[l,r),左右界,若所查找的是最后一个元素,右界为N+1,要求的int类型无法满足2147483647 + 1 的右界索引
第二次:
仍然采取 [l,r],直到 l==r-1 (即 l < r-1,结束时 l==F r==T)
关键点:界限收缩
没考虑例外:全T 没有F(-1索引未定义)
根本的问题是保留了两个元素的[l,r],对指针的理解运用不够灵活,l与r是可以相等的
官方解答:
int left = 1, right = n;
while (left < right) { // 循环直至区间左右端点相同
int mid = left + (right - left) / 2; // 防止计算时溢出
if (isBadVersion(mid)) {
right = mid; // 区间 [left, mid) 中 (mid为T)
} else {
left = mid + 1; // 区间 [mid+1, right) 中
//(mid+1未知,但right一定是T)
}
}
// 此时有 left == right,区间缩为一个点,即为答案
return left;
采取 [l,r),抛弃mid再分割为两个区间
保证了右边界一定是T即可,将左边界一直收缩至l=r都没问题
while (left < right)是因为最后一次操作后已经l=r,直接输出就行
3. 搜索插入位置 35
按第一题 [l, r] 方法解决时的新问题
- 若数组只有一个,不进行循环
- 右界无法保证大于等于TARGET,有可能TARGET比所有数都大,即输出的取值范围为:[0, numsSize]
解决:
//while循环已结束
if (nums[right] < target)
return right +1;
return right;
按第二题方法:
int left = 0 , right = numsSize-1 , mid;
while (left <= right)
{
mid = left + (right - left)/2;
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid +1;
else if (nums[mid] > target)
right = mid -1;
}
return left; //此处-1改为left即可
为什么只要改一处就行呢
若数组中有TG,循环内即已解决
若数组中无TG,
(倒数第二次循环有两个元素,一个小于TG(记为A)一个大于TG(记为B),mid为A)
最后一次循环有一个元素,即A,mid仍是A,且小于TG
所以left++,即B的位置
而实际上因为已排序,B之前所有元素都小于等于A,即小于TG
left自然是插入的位置
4. 寻找某可能元素的左侧边界
同理第二题[l, r)
int left = 0, right = numsSize;
while (left < right) {
int mid = left + (right - left)/2;
if (nums[mid] == target) {
right = mid; //相同时先不急着返回,向左收缩看有没有重复
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left;
5. 寻找某可能元素的右侧边界
int left = 0, right = numsSize;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
left = mid + 1; //向右收缩
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1; //最后一次循环也有left++,即一定越过一位
6. 统一
采用第一题的变式 [l, r]
- 若要重复元素的边界,要在==条件下将边界向左右收缩
- 若不存在该元素,需要判断索引是否越界(比所有元素都大)
- 本质 :两端对称的范围端点,每次分去一侧实现收缩
采用第二题的变式 [l, r)
- 若要重复元素的边界,要在==条件下将边界向左右收缩
- 若求右侧边界需要返回时-1
- 本质:r) 一侧有固定的性质(e.g. > TG),因而可以剔去,依托单侧进行二分收缩
二者的本质区别是二分时对区间的划分不同
以至最后一次操作后结果有些小区别,或是对特殊情况的解决方式不同
参考 labuladong(LC)