二分查找小总结

二分查找

第一次刷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

问题:
  1. 第一个元素没有左邻
  2. 此时用的是[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] 方法解决时的新问题
  1. 若数组只有一个,不进行循环
  2. 右界无法保证大于等于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]
  1. 若要重复元素的边界,要在==条件下将边界向左右收缩
  2. 若不存在该元素,需要判断索引是否越界(比所有元素都大)
  3. 本质 :两端对称的范围端点,每次分去一侧实现收缩
采用第二题的变式 [l, r)
  1. 若要重复元素的边界,要在==条件下将边界向左右收缩
  2. 若求右侧边界需要返回时-1
  3. 本质:r) 一侧有固定的性质(e.g. > TG),因而可以剔去,依托单侧进行二分收缩

二者的本质区别是二分时对区间的划分不同
以至最后一次操作后结果有些小区别,或是对特殊情况的解决方式不同

参考 labuladong(LC)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值