模板讲解
二分的本质是在一段具有不同性质的连续区间中搜索边界点的过程,如下图所示
![[二分_1.png]]
如果我们将红色区间的性质设定为函数check()
,那么我们就有搜索左侧边界点的基础模板
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
这个模板中有几个需要注意的点:
- 当我们检查
mid
的性质时,因为我们是要找左侧边界点,所以当mid满足红色区间的性质时,左侧边界点可能存在的区间为[mid, r]
,因此我们令l = mid
- 当mid不满足红色区间的性质时,左侧边界点可能存在的区间为
[l, mid - 1]
,因此我们令r = mid - 1
- 由于
>>
是向下取整,mid
的取值是靠近l
的,但是我们找到左侧边界点实际上是依靠r
找到的(如果l
先到左侧边界点,指针mid
和l
重合,此时出现死循环)因此如果我们要使mid
的取值是靠近r
就要int mid = (l + r + 1) >> 1
同理我们得到搜索右侧边界点的基础模板
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (check(mid)) l = mid + 1;
else r = mid;
}
这个模板中有几个需要注意的点:
- 当我们检查
mid
的性质时,因为我们是要找右侧边界点,所以当mid满足红色区间的性质时,右侧边界点可能存在的区间为[mid + 1, r]
,因此我们令l = mid + 1
- 当mid不满足红色区间的性质时,左侧边界点可能存在的区间为
[l, mid]
,因此我们令r = mid
- 由于
>>
是向下取整,mid
的取值是靠近l
的,而且我们找到右侧边界点是依靠l
找到的,所以此时使用要int mid = (l + r) >> 1
没有问题
LeetCode例题
704. 二分查找
可以通过寻找左侧边界点或者右侧边界点的方式解决这个问题,只是使用不同的check()
,如果将target
看作左侧边界点那么对应的check()
就是nums[mid] <= target
,如果将target
看作右侧边界点那么对应的check()
就是nums[mid] < target
左侧边界点对应第一个模板
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
if (nums[l] == target) return l;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (nums[mid] <= target) l = mid;
else r = mid - 1;
}
if (nums[r] == target) return r;
else return -1;
}
};
右侧边界点对应第二个模板
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
if (nums[l] == target) return l;
while (l < r) {
int mid = (l + r) >> 1;
if (nums[mid] < target) l = mid + 1;
else r = mid;
}
if (nums[l] == target) return l;
else return -1;
}
};
35. 搜索插入位置
首先我们考虑一些特殊情况
- 如果
target
小于等于区间的左边界返回l
- 如果
target
大于区间的右边界返回r + 1
- 如果
target
等于区间的右边界返回r
还有一种情况是nums[l] < target < nums[r]
,我们通过寻找左侧边界点的方式思考这个问题,选择nums[mid] <= target
作为check()
,最终通过r
指针找到target
(如果是区间中不包含target
的情况r
指针指向距离target
最近偏小的值)
综上所述得到代码如下
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
if (target <= nums[l]) return l;
if (target > nums[r]) return r + 1;
if (target == nums[r]) return r;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (nums[mid] <= target) l = mid;
else r = mid - 1;
}
if (nums[r] == target) return r;
else return {r + 1};
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
这是一个非常适合二分算法的题目,我们套用两个模板解决这个问题
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if (nums.empty()) return {-1, -1};
int l = 0, r = nums.size() - 1;
int leftBorder = 0, rightBorder = 0;
while (l < r) {
int mid = (l + r) >> 1;
if (nums[mid] < target) l = mid + 1;
else r = mid;
}
if (nums[l] != target) return {-1, -1};
else {
leftBorder = l;
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (nums[mid] <= target) l = mid;
else r = mid - 1;
}
rightBorder = r;
}
return {leftBorder, rightBorder};
}
};