整数二分
898 字
4 分钟
整数二分

整数二分
整数二分最容易让人混乱的地方不是循环,而是边界。很多题不是直接问“某个数在哪里”,而是问:
- 第一个满足条件的位置在哪里。
- 最后一个满足条件的位置在哪里。
这两种问题对应两套模板。
1、什么时候可以二分
只要答案区间具有单调性,就可以考虑二分。
比如一个数组满足:
小于 x 的部分 | 大于等于 x 的部分那么“第一个大于等于 x 的位置”就可以二分。
再比如:
满足条件的部分 | 不满足条件的部分或者反过来,只要能被某个 check(mid) 分成两段,就能二分。
2、模板一:找左边界
当区间被分成:
[l, mid] 和 [mid + 1, r]通常是在找第一个满足条件的位置。
int bsearch_left(int l, int r) { while (l < r) { int mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } return l;}这里 check(mid) 表示:答案可能在 mid 或 mid 左边。
3、模板二:找右边界
当区间被分成:
[l, mid - 1] 和 [mid, r]通常是在找最后一个满足条件的位置。
int bsearch_right(int l, int r) { while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid)) l = mid; else r = mid - 1; } return l;}这里 mid 要写成:
int mid = (l + r + 1) >> 1;原因是当 l + 1 == r 时,如果还用 (l + r) >> 1,mid 会等于 l,可能导致死循环。
4、例子:有序数组中查找 x 的范围
给定有序数组:
1 2 2 2 3 4查找 2 第一次和最后一次出现的位置。
左边界是第一个 >= 2 的位置。
右边界是最后一个 <= 2 的位置。
5、完整代码
#include <bits/stdc++.h>using namespace std;
const int N = 100010;
int n, q;int a[N];int x;
bool check_left(int mid) { return a[mid] >= x;}
bool check_right(int mid) { return a[mid] <= x;}
int bsearch_left(int l, int r) { while (l < r) { int mid = (l + r) >> 1; if (check_left(mid)) r = mid; else l = mid + 1; } return l;}
int bsearch_right(int l, int r) { while (l < r) { int mid = (l + r + 1) >> 1; if (check_right(mid)) l = mid; else r = mid - 1; } return l;}
int main() { cin >> n >> q; for (int i = 0; i < n; i++) cin >> a[i];
while (q--) { cin >> x;
int l = bsearch_left(0, n - 1); if (a[l] != x) { cout << "-1 -1" << endl; } else { int r = bsearch_right(0, n - 1); cout << l << " " << r << endl; } }
return 0;}6、如何判断用哪个模板
可以先问一句:
答案更像是左边界,还是右边界?如果是找“第一个满足条件”,用左边界模板。
如果是找“最后一个满足条件”,用右边界模板。
7、容易写错的地方
(1)右边界模板忘记 +1
int mid = (l + r + 1) >> 1;这是防止死循环的关键。
(2)check 的含义不清楚
写二分前先用一句话定义 check(mid)。
比如:
check(mid):a[mid] 是否大于等于 x定义清楚后,更新边界就不容易乱。
(3)没有检查答案是否存在
二分只能找到边界,不能保证目标值一定存在。所以找到位置后还要判断:
if (a[l] != x)总结
整数二分不要硬背一堆变形。先记住两套模板:
找左边界:mid = (l + r) >> 1找右边界:mid = (l + r + 1) >> 1然后把 check(mid) 的含义写清楚,边界更新就会自然很多。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














