总结
二分查找可能会遇到哪些边界情况?为什么示例代码能完美的解决这些边界情况?
答:总是可以通过问题转换写出满足L < R
的优美代码。
- 二分查找伪代码
while( L < R ) {
int M = L + (R - L)/2;
if( 答案在[M + 1,R]中 ) { // 思考一下,什么情况下能够说明“答案在[M+1,R]中”
L = M + 1;
} else { // 答案在[L,M]中
R = M;
}
}
-
写二分查找遇到了死循环,考虑是不是遇到了“差一点”问题。
-
如果代码中是用的
L = M
,把L
不断往右push,那么M
向上取整(M = L + (R - L + 1)/2
); -
如果代码中是用的
R = M
,把R
不断往左push,那么M
向下取整(M = L + (R - L)/2
)。
-
-
代码示例:
- 有一个从小到大排好序的数组,你要找到第一个大于等于x的数字,应该怎么做?
输入n,x,以及一个长度为n的数组a(已经从小到大排好序了)
输入样例:
9 4
2 3 3 3 3 4 4 4 4
- 代码样例:
-
#include <iostream> using namespace std; int n, x, a[100000]; int main() { cin >> n >> x; // n为数组元素个数,x为 // 输入数组 for( int i = 0; i < n; ++i ) cin >> a[i]; // 考虑数组中不存在大于等于x的数字的情况 if( x > a[n-1] ) { cout << -1 << endl; return 0; } // 二分查找 int L = 0, R = n-1; // 数组下标从0到n-1,闭区间 while( L < R ) { // 当区间中至少有两个数字的时候,需要继续二分 int M = L + (R - L) / 2; // 求出区间中点 if( a[M] < x ) { // 答案一定出现在[M+1,R]中 L = M + 1; } else { // a[M] >= x,答案一定出现在[L,M]中 R = M; } } // 此时L == R,a[L]就是第一个大于等于x的数字 if ( a[L] == x) { cout << L << endl; // 如果答案存在,则输出答案 } else { cout << -1 << endl; // 如果答案不存在,则输出-1 } return 0;
最后,再回顾一下在上一知识点中,我们推导了二分查找的时间复杂度。只有当我们询问区间中点的时候,我们才能让可行区间的长度以最快的速度变短——每次大约变为原来长度的一半,所以二分查找的时间复杂度是log_2(n)log2(n)。
二分查找时间复杂度的计算方法:
比如,在猜数字的游戏中,假设我们一开始有n个数字。每次把剩余数字的区间分成两半,直到xx次后只剩下最后一个数字,就是我们想要的答案啦。 计算公式如下:
n * 1/(2^x) = 1n∗1/(2x)=1
xx次后只剩下最后一个数字
x = log_2(n)x=log2(n)
那么,xx的值就是log_2(n)log2(n)咯