二分搜索
问题
描述:
给一个长度为n的单调递增的正整数序列,即序列中每一个数都比前一个数大。有m个询问,每次询问一个x,问序列中最后一个小于等于x的数是什么?
输入:
第一行两个整数n,m。
接下来一行n个数,表示这个序列。
接下来m行每行一个数,表示一个询问。
输出:
输出共m行,表示序列中最后一个小于等于x的数是什么。假如没有输出-1。
样例输入:
5 3
1 2 3 4 6
5
1
3
样例输出:
4
1
3
分析
我们用Left表示询问区间的左边界,用Right表示询问区间的右边界,[Left,Right]组成询问区间。一开始Left=1,Right=n,我们可以把原始序列的左边想象成若干个无穷小的数,把序列的右边想象成无穷大的数,这样比较好理解。序列已经按照升序排好,保证了二分的有序性。
每一次二分,我们这样来做:
①取区间中间值Mid=(Left+Right)/2;
②判断Mid与x的关系,如果a[Mid]>x,由于序列是升序排列,所以区间[Mid,Right]都可以被排除,修改右边界Right=Mid-1;
③如果a[Mid]<=x,修改左边界Left=Mid+1;
重复执行二分操作直到Left>Right。
下面我们来分析答案的情况。循环结束示意图如下:LeftRight大于x小于等于x
最终循环结束时一定是Left=Right+1,根据二分第②步的做法我们知道Right的右边一定都是大于x的,而根据第③步我们可以知道Left左边一定是小于等于x的。
所以,一目了然,最终答案是Right指向的数。Right=0就是题目中输出-1的情况。
代码
#include <iostream>
using namespace std;
int n,m,a[110000];
int main()
{
cin >> n >> m;
for (int i=1; i<=n; i++) cin >> a[i];
a[0]=-1;
for (int i=1; i<=m; i++)
{
int x;
int left=1,right=n,mid;
cin >> x;
while (left <= right)
{
mid = (left + right) / 2;
if (a[mid] <= x) left = mid + 1;
else right = mid - 1;
}
cout << a[right] << endl;
}
return 0;
}