【题目链接】
ybt 1240:查找最接近的元素
OpenJudge NOI 1.11 01:查找最接近的元素
【题目考点】
1. 二分
- 二分查找大于等于某个数的最小值
- 二分查找小于等于某个数的最大值
- 二分查找某个数存在的最小区间
【解题思路】
一个非降序列a中有n个数字。要查找a中与数字x最接近的数字。
- 如果x小于a中最小的数字 a 1 a_1 a1,那么与x最接近的数字是 a 1 a_1 a1。
- 如果x大于a中最大的数字 a n a_n an,那么与x最接近的数字是 a n a_n an。
- 如果
a
1
≤
x
≤
a
n
a_1 \le x \le a_n
a1≤x≤an,那么序列a中一定存在相邻两个数字
a
i
a_i
ai与
a
i
+
1
a_{i+1}
ai+1,满足
a
i
≤
x
≤
a
i
+
1
a_i \le x \le a_{i+1}
ai≤x≤ai+1
- 如果 ∣ x − a i ∣ > ∣ x − a i + 1 ∣ |x-a_i| > |x-a_{i+1}| ∣x−ai∣>∣x−ai+1∣,那么 a i + 1 a_{i+1} ai+1是最接近x的数字。
- 如果 ∣ x − a i ∣ < ∣ x − a i + 1 ∣ |x-a_i| < |x-a_{i+1}| ∣x−ai∣<∣x−ai+1∣,那么 a i a_i ai是最接近x的数字。
- 如果 ∣ x − a i ∣ = ∣ x − a i + 1 ∣ |x-a_i| = |x-a_{i+1}| ∣x−ai∣=∣x−ai+1∣,那么 a i a_i ai与 a i + 1 a_{i+1} ai+1都是最接近x的数字,根据题目要求,要输出的数字是较小的 a i a_i ai。
以下有三种方法确定 a i a_i ai与 a i + 1 a_{i+1} ai+1
解法1. 二分查找某个数存在的最小区间
二分查找时,区间左端点为
l
l
l,右端点为
r
r
r,在通过二分查找缩短区间的过程中,始终保证
a
l
≤
x
≤
a
r
a_l \le x \le a_r
al≤x≤ar,取右半边时l=m
,取左半边时r=m
,最后在l+1==r
时跳出循环,此时
l
l
l即为上述要找的满足
a
i
≤
x
≤
a
i
+
1
a_i \le x \le a_{i+1}
ai≤x≤ai+1的
i
i
i,
r
r
r为
i
+
1
i+1
i+1。
解法2:二分查找大于等于x的最小值
中点m=(l+r)/2
,取中间偏左位置。取左半边时,r = m
,取右半边时l = m+1
,在l==r
时跳出循环。最终找到的元素下标为
l
l
l。此时
l
−
1
l-1
l−1即为满足
a
i
≤
x
≤
a
i
+
1
a_i \le x \le a_{i+1}
ai≤x≤ai+1的
i
i
i,
l
l
l为
i
+
1
i+1
i+1。
解法3:二分查找小于等于x的最大值
中点m=(l+r+1)/2
,取中间偏右位置。取左半边时,r = m-1
,取右半边时l = m
,在l==r
时跳出循环。最终找到的元素下标为
l
l
l。此时
l
l
l即为满足
a
i
≤
x
≤
a
i
+
1
a_i \le x \le a_{i+1}
ai≤x≤ai+1的
i
i
i,
l
+
1
l+1
l+1为
i
+
1
i+1
i+1。
【题解代码】
解法1. 二分查找某个数存在的最小区间
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, a[100005], x, l, r, m;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
cin >> m;
while(m--)//m组查询
{
cin >> x;
if(x < a[1])
cout << a[1] << endl;
else if(x > a[n])
cout << a[n] << endl;
else
{
l = 1, r = n;
while(l + 1 < r)//二分搜索 大于等于x的最小值
{
int mid = (l+r)/2;
if(a[mid] >= x)
r = mid;
else
l = mid;
}
if(abs(a[l]-x) <= abs(a[r]-x))//此时a[l] <= x <= a[r],二者相等时,取较小值
cout << a[l] << endl;
else
cout << a[r] << endl;
}
}
return 0;
}
解法2:二分查找大于等于x的最小值
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, a[100005], x, l, r, m;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
cin >> m;
while(m--)//m组查询
{
cin >> x;
if(x < a[1])
cout << a[1] << endl;
else if(x > a[n])
cout << a[n] << endl;
else
{
l = 1, r = n;
while(l < r)//二分搜索 大于等于x的最小值
{
int mid = (l+r)/2;
if(a[mid] >= x)
r = mid;
else
l = mid + 1;
}
if(abs(a[l]-x) < abs(a[l-1]-x))//此时a[l-1] <= x <= a[l],二者相等时,取较小值
cout << a[l] << endl;
else
cout << a[l-1] << endl;
}
}
return 0;
}
解法3:二分查找小等于x的最大值
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, a[100005], x, l, r, m;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
cin >> m;
while(m--)//m组查询
{
cin >> x;
if(x < a[1])
cout << a[1] << endl;
else if(x > a[n])
cout << a[n] << endl;
else
{
l = 1, r = n;
while(l < r)//二分搜索 大于等于x的最小值
{
int mid = (l+r+1)/2;
if(a[mid] <= x)
l = mid;
else
r = mid-1;
}
if(abs(a[l]-x) <= abs(a[l+1]-x))//此时a[l] <= x <= a[l+1]。二者相等时,取较小值
cout << a[l] << endl;
else
cout << a[l+1] << endl;
}
}
return 0;
}