二分查找(搜索区间为左闭右开)

二分查找有几种写法?它们的区别是什么? - Jason Li的回答

引言

二分查找的过程是不断把搜索边界缩小,往中间“挤”出结果的过程。

二分查找难点

假设 l l l是当前搜索区间左边界, r r r是当前搜索区间右边界, m m m是向下取整中位点, A A A为有序表。
之后示例中 l 0 l_0 l0 r 0 r_0 r0为初始边界, v v v为目标值

  • 循环条件: l < r   o r   l ≤ r l < r \ or \ l \le r l<r or lr
  • 边界调整: l = m   o r   l = m + 1 ; r = m   o r   r = m − 1 l = m \ or \ l = m + 1; r = m \ or \ r=m-1 l=m or l=m+1;r=m or r=m1
  • 返回结果: l   o r   l + 1   o r   r   o r   r + 1 l \ or \ l+1 \ or \ r \ or \ r+1 l or l+1 or r or r+1

以上问题可以通过取左闭右开的搜索区间来尝试解决,最终结论如下

  • 循环条件: l < r l<r l<r
  • 边界调整: l = m + 1 , r = m l = m + 1, r = m l=m+1r=m
  • 返回结果: l   o r   r l \ or \ r l or r

个人感性认识上,正是因为二分除法中,中位数有两种取法,而左闭右开选择了向下取整的中位数,来减少讨论。

C++STL库实现的二分查找函数,提供的参数也是相同的思路
lower_bound(first,last,value):从下标[first, last)内,二分查找第一个大于或等于value的数字
upper_bound(first,last,value):从下标[first, last)内,二分查找第一个大于value的数字

lower_bound的相同效果实现思路

// 求 第一个 x >= v
while (l < r) { 
    m = l + ((r - l) >> 1); // 防溢
    if (A[m] < v) l = m + 1;
    else r = m;
}
return l; // 或 return r;

循环条件

在查找过程中循环继续的条件

  • [ l , r ) [l,r) [l,r)不为空,需要继续搜索
  • 此时 [ l 0 , l ) [l_0,l) [l0,l)所有元素小于 v v v
  • 此时 [ r , r 0 ) [r,r_0) [r,r0)所有元素大于等于 v v v

边界调整

  • A [ m ] < v A[m]<v A[m]<v m m m应该被划分在 [ l 0 , l ) [l_0,l) [l0,l)内,应调整 l = m + 1 l = m + 1 l=m+1
  • A [ m ] ≥ v A[m] \ge v A[m]v m m m应该被划分在 [ r , r 0 ) [r,r_0) [r,r0)内,应调整 r = m r = m r=m

返回结果

最终循环结束时,结果为第一个大于等于 v v v的元素下标

  • [ l , r ) [l,r) [l,r)为空, l = r l = r l=r
  • [ l 0 , l ) [l_0,l) [l0,l)所有元素小于 v v v
  • [ r , r 0 ) [r,r_0) [r,r0)所有元素大于等于 v v v

upper_bound的相同效果实现思路

// 求 第一个 x > v
while (l < r) { 
    m = l + ((r - l) >> 1);
    if (A[m] <= v) l = m + 1; // 差别在此,A[m] == v 的归属
    else r = m;
}
return l; // 或 return r;

循环条件

在查找过程中循环继续的条件

  • [ l , r ) [l,r) [l,r)不为空,需要继续搜索
  • 此时 [ l 0 , l ) [l_0,l) [l0,l)所有元素小于等于 v v v
  • 此时 [ r , r 0 ) [r,r_0) [r,r0)所有元素大于 v v v

在查找过程中循环继续的条件

边界调整

  • A [ m ] ≤ v A[m]\le v A[m]v m m m应该被划分在 [ l 0 , l ) [l_0,l) [l0,l)内,应调整 l = m + 1 l = m + 1 l=m+1
  • A [ m ] > v A[m] > v A[m]>v m m m应该被划分在 [ r , r 0 ) [r,r_0) [r,r0)内,应调整 r = m r = m r=m

返回结果

最终循环结束时,结果为第一个大于 v v v的元素下标

  • [ l , r ) [l,r) [l,r)为空, l = r l = r l=r
  • [ l 0 , l ) [l_0,l) [l0,l)所有元素小于等于 v v v
  • [ r , r 0 ) [r,r_0) [r,r0)所有元素大于 v v v

其他

可以用lower_boundupper_bound解决常用二分查找问题如下

  • 小于 v v v的上界 lower_bound(l,r,v) - 1
  • 小于等于 v v v的上界 upper_bound(l,r,v) - 1
  • 大于等于 v v v的下界 lower_bound(l,r,v)
  • 大于 v v v的下界 upper_bound(l,r,v)

中间两个较为常用

LeetCode 35.搜索插入位置

int searchInsert(int* nums, int numsSize, int target)
{
    int l, r;
    l = 0, r = numsSize;
    while (l < r) { 
        int m = l + ((r - l) >> 1);
        if (nums[m] < target) l = m + 1;
        else r = m;
    }
    return l;
}

LettCode 34.在排序数组中查找元素的第一个和最后一个位置

int* searchRange(int* nums, int numsSize, int target, int* returnSize)
{
    int l = 0, r = numsSize, m;
    int* ret = malloc(sizeof(int) * 2);
    *returnSize = 2;
    ret[0] = ret[1] = -1;

    if (numsSize == 0) return ret;

    while(l < r) {
        m = l + ((r - l) >> 1);
        if (nums[m] < target) l = m + 1;
        else r = m;
    }
    if (r != numsSize && nums[l] == target) ret[0] = l;

    l = 0, r = numsSize;
    while (l < r) {
        m = l + ((r - l) >> 1);
        if (nums[m] <= target) l = m + 1;
        else r = m;
    }  
    if (l != 0 && nums[l - 1] == target) ret[1] = l - 1;

    return ret;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值