二分查找,找到左右端点
1. 简单二分查找
这里的简单,指的是,数组中没有重复元素,并且查找的值一定在数组中。
可以有以下两种写法👇
// 普通find函数(不允许有重复值)
int bi_find1(vector<int>& nums, int t) {
int l = 0, r = nums.size() - 1;
int mid;
while (l < r) {
mid = l + ((r - l) >> 1);
if (nums[mid] <= t) l = mid;
else r = mid - 1;
}
return l;
}
// 普通find函数(可以有重复值 —— 如果有重复返回的是最左边界)
int bi_find2(vector<int>& nums, int t) {
int l = 0, r = nums.size() - 1;
int mid;
while (l < r) {
mid = l + ((r - l) >> 1);
if (nums[mid] >= t) r = mid;
else l = mid + 1;
}
return l;
}
其中l + ((r - l) >> 1);
是为了防止溢出。
为什么写while(l < r)
, 而不是while(l <= r)
?—— 防止死循环,此外,还有好处是,每次循环结束的条件,其实是l == r
,也就是最终l = r
,就不用考虑返回值到底是l
还是r
了。
2. 找到左右端点
这里的二分查找找到左右端点,指的是,数组中有重复值,找到第一次和最后一次出现的位置。
主要的问题是:
-
使用
if (nums[mid] <= t) l = mid; else r = mid - 1;
(即bi_find1()
的核心部分) -
还是
if (nums[mid] >= t) r = mid; else l = mid + 1;
(即bi_find2()
的核心部分)。
这里先不给出代码,容易看懵。解决方案,举例(举例的时候,只举用两个数字的数组进行举例就行——保证正确)。
如图所示,nums[] = {9, 9}; l = 0, r = 1;
那么mid = 0;
如果是找左边界,我们显然需要r
左移(因为结束条件是l == r
,因此此时应该是l
保持不变,r
左移),因此此时是对r
赋值。—— 即使用if (nums[mid] >= t) r = mid;
。
如果是找右边界,我们显然需要l
右移,因此此时是对l
赋值。—— —— 即使用if (nums[mid] <= t) l = mid;
。—— 但是右边界还有一个问题,可以发现,l
永远移动不到r
上,因此我们取mid
的时候,可以用mid = (l + r + 1) >> 2;
。
左右边界代码👇
// 找到最左边界(同时,如果t不存在,那么返回的是t应该放到的位置)
int bi_find_left(vector<int>& nums, int t) {
int l = 0, r = nums.size() - 1;
int mid;
while (l < r) {
mid = l + ((r - l) >> 1);
if (nums[mid] >= t) r = mid;
else l = mid + 1;
}
return l;
}
// 找到最右边界
int bi_find_right(vector<int>& nums, int t) {
int l = 0, r = nums.size() - 1;
int mid;
while (l < r) {
mid = (r + l + 1) >> 1;
if (nums[mid] <= t) l = mid;
else r = mid - 1;
}
return l;
}
3. 找到应该插入到的位置
使用普通的二分查找就可以。
4. 代码示例
4.1 代码
// g++ bi_find.cpp -o bi_find
// ./bi_find
#include <bits/stdc++.h>
using namespace std;
// 普通find函数(不允许有重复值)
int bi_find1(vector<int>& nums, int t) {
int l = 0, r = nums.size() - 1;
int mid;
while (l < r) {
mid = l + ((r - l) >> 1);
if (nums[mid] <= t) l = mid;
else r = mid - 1;
}
return l;
}
// 普通find函数(可以有重复值 —— 如果有重复返回的是最左边界)
int bi_find2(vector<int>& nums, int t) {
int l = 0, r = nums.size() - 1;
int mid;
while (l < r) {
mid = l + ((r - l) >> 1);
if (nums[mid] >= t) r = mid;
else l = mid + 1;
}
return l;
}
// 找到最左边界(同时,如果t不存在,那么返回的是t应该放到的位置)
int bi_find_left(vector<int>& nums, int t) {
int l = 0, r = nums.size() - 1;
int mid;
while (l < r) {
mid = l + ((r - l) >> 1);
if (nums[mid] >= t) r = mid;
else l = mid + 1;
}
return l;
}
// 找到最右边界
int bi_find_right(vector<int>& nums, int t) {
int l = 0, r = nums.size() - 1;
int mid;
while (l < r) {
mid = (r + l + 1) >> 1;
if (nums[mid] <= t) l = mid;
else r = mid - 1;
}
return l;
}
int main() {
vector<int> nums = {0, 1, 4, 6, 6, 6, 10, 92, 100};
// 找到单一元素出现的位置
cout << "bi_find1() pos = " << bi_find1(nums, 1) << endl; // 1
cout << "bi_find2() pos = " << bi_find2(nums, 1) << endl; // 1
// 第一次出现的位置
cout << endl;
cout << "bi_find_left() pos = " << bi_find_left(nums, 1) << endl; // 1
cout << "bi_find_left() pos = " << bi_find_left(nums, 6) << endl; // 3
// 最后一次出现的位置
cout << endl;
cout << "bi_find_right() pos = " << bi_find_right(nums, 1) << endl; // 1
cout << "bi_find_right() pos = " << bi_find_right(nums, 6) << endl; // 5
// 找到不存在的元素的位置
cout << endl;
cout << "bi_find1() pos = " << bi_find1(nums, 2) << endl; // 1
cout << "bi_find2() pos = " << bi_find2(nums, 2) << endl; // 2
exit(0);
}
4.2 运行方法(Linux下)
上面代码保存到bi_find.cpp
然后使用下面方法编译运行。
g++ bi_find.cpp -o bi_find
./bi_find
4.3 运行结果
levi@LEVI1:~/code$ g++ bi_find.cpp -o bi_find
levi@LEVI1:~/code$ ./bi_find
bi_find1() pos = 1
bi_find2() pos = 1
bi_find_left() pos = 1
bi_find_left() pos = 3
bi_find_right() pos = 1
bi_find_right() pos = 5
bi_find1() pos = 1
bi_find2() pos = 2
ode$ ./bi_find
bi_find1() pos = 1
bi_find2() pos = 1
bi_find_left() pos = 1
bi_find_left() pos = 3
bi_find_right() pos = 1
bi_find_right() pos = 5
bi_find1() pos = 1
bi_find2() pos = 2