二分查找
🛐线性枚举
线性枚举问题类型
给定一个单调不降的有序数组如下所示,要求找到如下要求的元素
1)>6的最小数的下标位置
2)>=6的最小数的下标位置
3)<6的最大数下标位置
4)<=6的最大数的下标位置
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
1 | 3 | 4 | 6 | 6 | 6 | 7 | 8 | 9 |
3 | 2 | 4 | 1 |
/*
本函数将在线性枚举过程中多次调用,起到判断作用
*/
int compare_fun(int num, int target) { // num为遍历到的数,target在本题中为6
return num > target; // ---1
return num >= target; // ---2
return num <= target; // (注意,此时主函数中下标为idx-1) ---3
return num > target; // (注意,此时主函数中下标为idx-1) ---4
}
🚆时间复杂度:O( n n n) 效率较低
🚓二分枚举
每次将区间折半,进行折半查找,这种方式又称作折半查找,时间复杂度为O( l o g 2 n log_2n log2n)
引入例题:
🦼二分枚举模板
bool isGreen(int x, int y) { // 判断函数
return x == y;
}
int binarySearch(int *arr, int arrSize, int x) {
int l = -1, r = arrSize; // (1) 注意,l和r的初始值都不在数组索引范围内
int mid;
while(r - l > 1) { // (2) 当左右相差为1时,说明已经来到分界点
mid = l + (r - l) / 2; // (3) ⭐防溢出的保险写法
if( isGreen(arr[mid], x) ) // (4) x是指绿色所代表的颜色
r = mid; // (5) 如果mid所指地点为绿色,则mid-r都是绿色
else
l = mid; // (6) 如果mid所指地点为红色,则l-mid都是红色
}
return r; // (7) 返回下标最小的绿色元素
// 本模板最为灵活的地方在于其返回值:
// 当要返回最小绿色时,return r
// 当要返回最大红色时,return l
}
🚅迭代过程的实质以及其结束条件
迭代实质:红绿指针不断逼近红绿分界线的过程
结束条件:红绿指针指向红绿分界线。
初始化
红色游标初始值为:-1,绿色游标初始值为:n
如果使红色游标初始值为:0,绿色游标初始值为:n-1 当元素全红全绿的情况下违反了:
红色游标始终指向红色,绿色游标始终指向绿色
终点位置
⭐mid = ⌊ l + r 2 ⌋ \lfloor \frac{l+r}{2} \rfloor ⌊2l+r⌋
l的最小值是-1,r的最小值是1+2,所以mid = 0 // 极限情况,仅有一个元素时
r的最大值为n,l的最大值为r-2,所以mid = n-1 ----- 由此中点的位置 [ 0 , n ) [0, n) [0,n)
🚚二分查找数组模板
// 通过修改isGreen函数可解决绝大部分二分查找问题
/************** 二分查找 数组 模板 **************/
/*
1)传参的数组满足:红红红红红红红红绿绿绿绿绿绿绿;
2)返回值:绿色区段的左边界;
*/
int isGreen(int val, int x);
int binarySearch(int *arr, int arrSize, int x) {
int l = -1, r = arrSize;
int mid;
while(l + 1 < r) {
mid = l + (r - l) / 2;
if( isGreen(arr[mid], x) )
r = mid;
else
l = mid;
}
return r;
}
/************** 二分查找 数组 模板 **************/
🚲二分查找具体应用
🛰数组元素的精确查找
// 二分查找实例应用
int isGreen(int val, int x);
int binarySearch(int *arr, int arrSize, int x) {
int l = -1, r = arrSize;
int mid;
while(l + 1 < r) {
mid = l + (r - l) / 2;
if( isGreen(arr[mid], x) )
r = mid;
else
l = mid;
}
return r;
}
/************** 二分查找 数组 模板 **************/
int isGreen(int val, int x) {
return val >= x; // (1)
}
int search(int* nums, int numsSize, int target){
int r = binarySearch(nums, numsSize, target); // (2) 找到红绿分界处的靠右分界点
if(r == numsSize || nums[r] != target) // (3) 找不到或者全部小于target的两种特殊情况
return -1;
return r; // (4)
}
有序数组一般可以优先考虑 二分查找。
✈线性枚举 + 数据的精确查找
class Solution {
public:
bool isGreen(int x, int target) {
return x >= target;
}
int binarySearch(vector<int>arr, int target) {
int len = arr.size();
int left = -1;
int right = len;
int mid;
while(right-left > 1) {
mid = left + (right - left) / 2;
if(isGreen(arr[mid], target)) {
right = mid;
}else {
left = mid;
}
}
return right;
}
vector<int> twoSum(vector<int>& nums, int target) {
int len = nums.size();
int left = -1;
int right = len;
vector<int>ans;
for(int i = 0; i < len; ++i) { // 线性枚举ing
ans.push_back(nums[i]);
int diff = target - nums[i]; // 二分查找目标元素
int right = binarySearch(nums, diff); // 二分查找ing
if(right == len || nums[right] != diff) { // 筛选出不合要求的下标
ans.clear(); // 容器清空
continue;
}
else {
ans.push_back(nums[right]);
break;
}
}
return ans;
}
};
// 时间复杂度:O(nlogn2)
// 注意:复杂度过高无法通过
🚤数组的模糊查找
- 1.大于等于x的最小值
class Solution {
public:
bool isGreen(int val, int target) {
return val >= target;
}
int binarySearch(vector<int>& nums, int target) {
int len = nums.size();
int left = -1;
int right = len;
int mid;
while(right - left > 1) {
mid = left + (right - left) / 2;
if(isGreen(nums[mid], target)) {
right = mid;
}else {
left = mid;
}
}
return right;
}
int searchInsert(vector<int>& nums, int target) {
return binarySearch(nums, target);
}
};
-
大于x的最小值
class Solution { public: bool isGreen(char val, char target) { return val > target; } int binarySearch(vector<char> letters, char target) { int len = letters.size(); int left = -1; int right = len; int mid; while(right - left > 1) { mid = left+(right-left)/2; if(isGreen(letters[mid], target)) { right = mid; }else { left = mid; } } return right; } char nextGreatestLetter(vector<char>& letters, char target) { int pos = binarySearch(letters, target); if(pos == letters.size()) return letters[0]; return letters[pos]; } };
-
小于等于x的最大值
bool isGreen(int val, int target) {
return val > target;
}
int binarySearch(vector<int> nums, int target) {
int len = nums.size();
int left = -1;
int right = len;
int mid;
while(right - left > 1) {
mid = left + (right - left) / 2;
if(isGreen(nums[mid], target)) {
right = mid;
}else {
left = mid;
}
}
return right-1;
}
int getIndex(vector<int> nums, int target) {
int pos = binarySearch(nums, target);
return pos;
}
- 小于x的最大值
bool isGreen(int val, int target) {
return val >= target;
}
int binarySearch(vector<int> nums, int target) {
int len = nums.size();
int left = -1;
int right = len;
int mid;
while(right - left > 1) {
mid = left + (right - left) / 2;
if(isGreen(nums[mid], target)) {
right = mid;
}else {
left = mid;
}
}
return right-1;
}
int getIndex(vector<int> nums, int target) {
int pos = binarySearch(nums, target);
return pos;
}
总结:
🚁单调函数的查找
class Solution {
public:
bool isGreen(int x, int target) {
return (long long)x*x > target; // 进行强制类型转化,防止数据溢出
}
int BinarySearch(int target) { // 这里的二分查找:大于的最小值
int left = 0;
int right = target;
int mid;
while(right - left > 1) {
mid = left + (right - left) / 2;
if(isGreen(mid, target)) {
right = mid;
}else {
left = mid;
}
}
return right-1; // 当开平方为整数时,right-1为开平方后的结果
// 开平方位小数时,righ-1为取整后的结果
}
int mySqrt(int x) {
if(x == 0 || x == 1) return x; // 1 and 0的平方是本身
return BinarySearch(x);
}
};
⛽二分枚举的通解:
- 抽象出一个单调函数
- 确定isGreen函数
- 二分枚举求出红绿边界
- 确定返回红色边界还是绿色边界