最近工作中遇到了二分搜索问题。它是计算机课程中最基础的算法,这是我们在顺序查找时,最直接的一种提速的普遍性算法了,能够将一个顺序的查找的时间复杂度,从O(n)提升到了O(logn),这是质变的算法。
首先我们来看一下一个经典的二分搜索算法:
int BinarySearch(vector<int>& vec_arr, int target) {
int left = 0;
int right = vec_arr.length() - 1;
while(left <= right) {
//这里之所以不直接left + right,是因为存在溢出的可能,所以实现的时候需要规避这个问题
int mid = left + (right - left) / 2;
if (vec_arr[mid] == target) {
return mid;
}
//目标在mid的右边,修改左边界
else if (target > vec_arr[mid]) {
left = mid + 1;
}
//目标在mid的左边,修改右边界
else {
right = mid - 1;
}
}
return -1;
}
这个算法不用多说,就是一个有序序列中查找一个目标值的位置信息,是最经典的一个场景。
下面总结几个可能的二分变种使用场景。
1)如果目标值有多个,找出第一个出现的值,即是左边界,直接阅读源码,看注释就明白
int BSFindFirstEqual(const vector<int>& vec_arr, int target) {
int left = 0;
int right = vec_arr.size() - 1;
//循环继续条件,这里必须是<=
while (left <= right) {
int mid = left + (right - left) / 2;
//这里需要重点关注的是,如果vec_arr[mid] == target的时候,由于需要寻找左边界,所以需要让right往左靠,所以满足相等的时候,执行right = mid - 1;
if (target <= vec_arr[mid]) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
//最后,如果可能找到目标,那就只可能是left位置,做下判断
if (vec_arr[left] == target && left < vec_arr.size()) {
return left;
}
return -1;
}
2)如果目标值有多个,找出最后一个出现的值,即是右边界,直接阅读源码,看注释就明白
int BSFindLastEqual(const vector<int>& vec_arr, int target) {
int left = 0;
int right = vec_arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
//情况与问题1类似,当vec_arr[mid]与target相等时,我们需要找右边界,那需要让left往右移动
if (target >= vec_arr[mid]) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
//最后,如果可能找到目标,那就只可能是left位置,做下判断
if (vec_arr[right] == target && right >= 0) {
return right;
}
return -1;
}
3)找到第一个大于等于target的数的位置
//这个核心算法,与找第一个与target相等的一样。区别在于,当找不到的时候,需要返回第一个比target大的数,即是left左指针
int BSFindFirstEqualLarger(const vector<int>& vec_arr, int target) {
int left = 0;
int right = vec_arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
//当vec_arr[mid] == target的时候,right往左靠
if (target <= vec_arr[mid]) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return left;
}
4)找第一个大于target的位置
int BSFindFirstLarger(const vector<int>& vec_arr, int target) {
int left = 0;
int right = vec_arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target >= vec_arr[mid]) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return left;
}
5)找最后一个小于等于target的值
int BSFindLastEqualSmaller(const vector<int>& vec_arr, int target) {
int left = 0;
int right = vec_arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target >= vec_arr[mid]) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return right;
}
6)找最后一个小于target的值
int BSFindLastSmaller(const vector<int>& vec_arr, int target) {
int left = 0;
int right = vec_arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target <= vec_arr[mid]) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return right;
}