一看就会,一写就废的二分查找 如何破?

本文详细解析了二分搜索在递增数组查找目标值和求平方根场景中的关键点,包括搜索区间的定义、收缩策略以及边界处理,强调理解核心问题的重要性,避免死循环和错误结果。
摘要由CSDN通过智能技术生成

摘要

优点:逻辑简单、易实现。
难点:边界问题易混淆。
核心:搜索区间的定义及收缩。

  最近遇到一个二分搜索问题,本以为比较简单,但是几次尝试,要么搜索结果错误,要么就是死循环,哎,还是小看到这个问题了。

  经过分析,问题主要出在边界处理问题上。在业务开发中边界问题往往是引发BUG的一个重灾区,在二分法中也不例外,leetcode上还总结出了相应的模板,可见踩坑之多。

  其实只要搞清楚核心问题,随时手撕代码。拒绝背模板,背代码(记忆超人请忽略…)。
图片

二分法的关键点

1、搜索区间的边界定义。

2、搜索区间的收缩。

3、搜索结束的条件。

4、搜索结束后,区间的两个指针位于什么位置。

场景一:递增数组,查找目标值的位置

我们根据下面的示例,围绕核心问题:“搜索区间不为空”,来依次查看几个关键点:

1、定义搜索区间,也就是定义边界:

该示例中采用左闭右开[left,right)。为什么选择左闭右开?个人习惯,你也可以选择左闭右闭[left,right],关键是明确搜索区间边界。

2、搜索区间的收缩:

区间指针left,right在每一轮搜索后,都要进行调整,这也是造成死循环的一个潜在问题。

1)、调整基点:基于中位数进行调整。

2)、调整方向:趋近于目标值的方向。

3、搜素失败的条件:

在[left,right)区间中,为了保持区间有效,必须保证额left<right,否则区间是无效的。

4、指针的位置:

假如未找到目标值,此时left=right,区间缩减为0,nums[left]是第一个大于target的数值。

/*
nums:递增数组
numSize: 数组长度
target:搜索的目标值
return: 目标值在数组中的索引,如果未找到,返回-1
*/
int search(int* nums, int numsSize, int target) {

if( nums == NULL || numSize == 0 ){
  return -1;
}
/*
1、初始化搜索区间
2、采用左闭右开[left,right)
3、left指向区间的左起始位置,
   right指向区间的结束位置
*/
int left=0,right=numsSize;

/*
1、循环结束条件
2、因为采用左闭右开
 1)当left=right时,
   区间长度为0,搜索结束
 2)当left>right时,
   区间长度为负,没有意义,搜索结束
 3)当left<right时,
   区间长度不为空,继续搜索
*/
while( left<right ){
    //区间长度
    int len = right-left;   
    //区间中位索引 
    int mid = len/2 + left; 

    if( nums[mid] == target ){
      return mid;
    }

    //[left,mid-1] mid [mid+1,right)
    if( target < nums[mid] ){
/*
1、目标值在区间左半边,向左收缩区间,
   right左移
2、当len=2时,mid=left+1,
   right左移后,区间长度缩小为1
3、当len=1时,mid=left,
   right左移后,区间缩小为0
*/
      right=mid;
    }else{
/*
1、目标值在区间的右半侧,向右收缩区间,
   left右移
2、当len=2时,mid=left+1,left右移后,
   left==right,区间长度缩小为0
3、当len=1时,mid=left,left右移后,
   left==right,区间长度缩小为0
*/
      left=mid +1 ;
    }
}

assert( left == right );

return -1;
}

场景二:求平方根,结果向下取整

下面的例子,我们仍然围绕核心:“搜索区间不为空”,并查看几个关键点:

1、定义搜索区间:

上个示例中,采用左闭右开[left,right),为了展示不同用法,这次我们可以选择左闭右闭区间[left,right],进一步明确区间的定义不用一成不变,关键是明确搜索区间边界 。

2、搜索区间的收缩:

和上一个示例一致,不再赘述。

3、搜索失败的条件:

在[left,right]区间中,为了保持区间有效,必须保证left<=right,否则区间是无效的。

4、指针的位置:

假如未找到目标值,此时left>right,nums[right]的平方时最接近,且不会超过目标值。

/*
求x的平方根,向下取整
*/
int mySqrt(int x) {
/*
1、初始化搜索区间
2、采用左闭右闭[left,right]
3、left指向区间的左起始位置,right指向区间的最后一个有效数值。
*/
int64_t left=0,right=x;

/*
1、循环结束条件
2、因为采用左闭右闭
 1)当left<=right时,区间长度不为空,继续搜索
 2)当left>right时,区间长度为负,没有意义
*/
while( left<=right ){
    
  int64_t len = right-left+1;  //区间长度
  int64_t mid = len/2 + left;  //中位数值
  int64_t x1 = mid * mid;      //中位数平方

  if( x == x1 ){
    return mid;
  }

  //[left,mid-1] mid [mid+1,right]
  if( x1 > x ){
/*
  1、mid平方x1大于目标值,
     搜索区间需要向左收缩
  2、当len==2时,
     mid == left+1 == right,
     收缩后,区间长度为1
  3、当len==1时,
     mid == left == right,
     收缩后,循环退出,
     1)[right,left] -> right跨过left,来到了left的左边。
     2)right的平方是最大且不超过目标值的整数。
     3)left的平方是最小且超过目标值的整数。
*/
    right=mid-1;
  }else{
/*
  1、mid平方x1小于目标值,
     搜索区间需要向右收缩
  2、当len==2时,
     mid == left+1 == right,
     收缩后,区间长度为1
  3、当len==1时,
     mid == left == right,
     收缩后,循环退出。
     1)[right,left] -> left 跨过right,来到了right的左边。
     2)right的平方是最大且不超过目标值的整数。
     3)left的平方是最小且超过目标值的整数。
*/
    left=mid+1;
  }
}

return right;
}

手敲一遍,胜过默背十遍。

在这里插入图片描述

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值