二分原理
- 整数的二分问题中最关键的是mid值的选取问题,有两种选取方式:
1) right + left >> 1;
2) right + left + 1 >> 1; - 那么这两种二分方式有什么区别呢? 以二分一个数组中的元素为例:
1) 数组中的元素是奇数个:无分别,都是选取中间元素
2) 数组中的元素是偶数个,前者的mid选取前半部分最后一个元素,后者选取后半部分第一个元素。
若是ture在右半边范围内,则用mid = L+R >> 1, 此时mid往左靠
若是true在左半边范围内,则用mid = L+R+1>>1,此时mid往右靠
算法题目
给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。
对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。
如果数组中不存在该元素,则返回“-1 -1”。
输入格式
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数(均在1~10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。
输出格式
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回“-1 -1”。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
算法模板
#include<iostream>
using namespace std;
const int N = 100010;
int q[N];
int n, m;
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++)
cin >> q[i];
while(m--){
int x;
cin >> x;
int l = 0, r = n-1;
while(l < r){
int mid = l + r >> 1;
//**true在数组元素的右半边范围内,故不需要+1**
if(q[mid] >= x) r = mid;
else l = mid+1;
}
if (q[l] != x)
cout << "-1 -1" << endl;
else{
cout << l << ' ';
l = 0;
r = n-1;
while(l < r){
int mid = l + r +1 >> 1;
//**ture在数组元素的左半边范围内,故需要+1**
if(q[mid] <= x)
l = mid;
else
r = mid-1;
}
cout << l << endl;
}
}
return 0;
}
题解二
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int v[N];
int* q_serch(int m, int left, int right)
{
int* temp = new int[2]; /* 用来存储返回值 */
int i = left, j = right;
// 找左边界
while(i < j){
int mid = i + j >> 1;
if(v[mid] >= m)
j = mid;
else
i = mid + 1;
}
if(v[i] != m){
temp[0] = -1;
temp[1] = -1;
return temp;
}
// 找右边界
else{
temp[0] = i;
i = left; j = right;
while(i < j){
int mid = i + j +1 >> 1;
if(v[mid] <= m)
i = mid;
else
j = mid - 1;
}
temp[1] = i;
return temp;
}
}
int main()
{
int n, q;
int *p = new int[2]; /* 存储查询到的两个下标的位置 */
scanf("%d%d", &n, &q);
for(int i = 0; i < n; i++)
scanf("%d", &v[i]);
for(int i = 0; i < q; i++){
int m;
scanf("%d", &m);
p = q_serch(m, 0, n-1);
printf("%d %d\n", p[0], p[1]);
}
return 0;
}
题解三
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int v[N];
void left_search(int k, int left, int right)
{
while(left < right){
int mid = left + right >> 1;
if(v[mid] >= k)
right = mid;
else
left = mid + 1;
}
if(v[left] != k)
cout << -1;
//left = -1;
else
cout << left;
// return;
}
void right_search(int k, int left, int right)
{
while(left < right){
int mid = left + right + 1 >> 1;
if(v[mid] <= k)
left = mid;
else
right = mid - 1;
}
if(v[left] != k)
cout << -1;
//left = -1;
else
cout << left;
//return;
}
int main()
{
//ios::sync_with_stdio(false);
int n, q;
int k; /* 表示查询 */
cin >> n >> q;
for(int i = 0; i < n; i++)
cin >> v[i];
while(q--){
cin >> k;
left_search(k, 0, n-1);
cout << ' ';
right_search(k, 0, n-1);
cout << endl;
}
return 0;
}
思考
- 为什么true在左半边范围内需要+1?
当true在数组左半边时,此时的二分有两种情况:
1) mid落在左边,此时为true:
L = mid, R 不变
2) mid落在右边,此时为false
R = mid-1,L不变
考虑当L = R-1;的情况时,如果mid = L + R >> 1 ,此时还是L ,此时为true 则二分区间还是[L,R],就陷入了死循环。故mid = L + R + 1 >> 1是正确做法。