二分查找的边界问题是怎么产生的?

总结:二分查找的目标有两个,一个是左区件的右边界,一个是右区间的左边界

如何去理解二分的过程?

如果要查找的是左区间的右边界

可以将[l, r]理解一个集合,这个集合范围内的数都有可能是最后需要得到的目标值左区间的右边界这个点

每次要做的事情就是去缩小(更新)这个集合,最终使得集合里只有一个元素,那个元素就是目标值

每一次用mid去找集合的中点,有一个大前提:结合的结构[需要的值的集合|不需要的值的集合]

为了方便理解这个集合,我们可以理解为假设你有一个飞镖(也就是mid),有一个靶子(就是这个数组),靶子有内环(边界点所在的区间或者说集合)和外环(我们要找的集合的补集),靶子有一个中心点(内环的边界,也就是需要找到左区间的右边界)。

mid的位置有两种情况:落在左区间里或者落在右区间

  • 如果落在了左区间,相当与告诉你了一个信息:从这个地方包括这个地方(mid所在的索引)往后([mid, r]),可能会发现右边界。与之对应的信息是右边界绝对不在mid左边的集合内([l, mid-1]),所以左边的集合([l, mid-1])就可以被删掉了,要实现这个操作只需要让下一次判断的集合是[mid, r]就i可以了,等价于l=mid
  • 如果落在了右区间,相当于告诉了你:这个点往右(包括这个点)都不是我要的区间,需要被删掉。那就直接令l = mid-1,删除集合中绝对不是答案的那一坨,也就是右边那一坨
  • 这样不断的往复删除确定的无用集合,最后就可以得到一个目标集合。

对于查找到是右区间的左边界也是一个原理。

mid的计算是左偏还是右偏怎么去判断?

关于mid的计算方式mid=i+j >> 1还是(mid=i+j>>1) + 1,个人有一种比较好理解的方法:

同样分两种情况:

  1. 找左区间右边界
  2. 找右区间左边界

左区间右边界

  • 首先要明白产生边界问题的原因在哪里

奇数个集合的时候,对于两者mid都是指向中间元素,但是如果集合个数是偶数,他们的中点是谁?很显然,两者都不是中点(1 中点是我 2中的那是12中间的文字吧)。

所以要么去文字左边的1为中点,要么取文字右边的2为中点。这样一来这个中点其实就不是真正意义上的中点了。

首先说结论,取右边界需要右偏,我们来分析一下:

对于集合很大的时候,是没有问题的,边界问题只会发生在集合很小的时候,所以只有拿出一个很小的集合,一个快被处理结束的集合,才能够知道为啥左偏会进入死循环,为啥不能用它这个问题。

所以直接去分析两个元素时有哪些情况:(O表示的是左区间右边界所在的集合,X表示待删除的集合)

  • [O, O]
  • [X, X]
  • [O, X]
  • [X, O]

其中,[X, O]这种情况不可能存在,因为左区间的相对顺序一定在左边

左边界l是指向第一个元素的,右边界r是指向第二个元素的

mid如果找到目标元素并不会剔除,并不会缩小范围,所以这个时候想要缩小范围就需要mid起作用了

对于[O, O]二分查找如果左偏会去看第一个元素,包含它,这没什么问题,只要我们的mid下一次去往右看就行了,但是问题就在于下一次mid看左边还是看右边是和左偏绑定在一起的,所以如果左偏,下一次mid还会看左边,如果右偏下一次mid还会看右边,但是我们需要看右边啊!所以只能够右偏了,这样遇到这种情况mid也能继续向右看去。

同样的对于右区间左边界的判断也是如此

  • [O, O]

其实说白了就是,两个元素时,找右边界时遇到需要的元素l会直接落在mid上进行更新。

找左边界时, 遇到需要的元素,r会直接落在mid上进行更新,为了防止更新后继续重合。

  • 找右边界,mid下一次查找只能偏右更新,避开l防止死掉

  • 找左边界,mid下一次查找就只能偏左更新,避开r防止死掉

附上代码以及练习的链接

练习的链接

Codes

#include <iostream>
using namespace std;

const int N = 1e5;
int arr[N], n, q, k, i, j;

void find(int target) {
    // 查找左边界
    i = 0, j = n - 1;
    while (i < j) {
		int mid = i + j >> 1;
		if (arr[mid] >= target) {
			j = mid;
		} else {
		    i = mid + 1;
		}
	}
	if (arr[i] == target) cout << i << ' ';
	else {
	    cout << -1 << ' ' << -1 << endl;
	    return;
	}
	// 查找右边界
    i = 0, j = n - 1;
	while (i < j) {
	    int mid = (i + j + 1) >> 1;
	    if (arr[mid] <= target) {
	        i = mid;
	    } else {
	        j = mid - 1;
	    }
	}
	cout << i << endl;
}

int main() {
    cin >> n >> q;
    for (int i = 0; i < n; i ++) {
        cin >> arr[i];
    }
	while (q --) {
	    cin >> k;
	    find(k);
	}
} 
  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值