一、二分查找基础
(1)int mid = ((right - left) >> 1) + left;
(2)lower_bound的底层实现
int lower_bound(vector<int>& nums, int x)
{int left = 0;int right = nums.size() - 1;// 区间为 左闭右闭while (left <= right) {int mid = left +(right - left) / 2;if (x > nums[mid]) {left = mid + 1;}else {right = mid - 1; }}return left;
}
upper_bound用法和上面类似。只是把lower_bound的 【大于等于】 换成 【大于】 。仿函数等等全是相同的用法
底层实现
int upper_bound(vector<int>& nums, int x) {int left = 0;int right = nums.size() - 1;while (left <= right) {int mid = left +(right - left) / 2;if (x >= nums[mid]) { //这里是大于等于left = mid + 1;}else {right = mid - 1; }}return left;
}
(3)binary_search()实现
template <class ForwardIterator, class T>
bool binary_search (ForwardIterator first, ForwardIterator last, const T& val)
{first = std::lower_bound(first,last,val);return (first!=last && !(val<*first));
}
(5)二分查找
int search(vector<int>& nums, int target){// 二分查找区间[left, right),初始为整个区间int left = 0; int right = nums.size();// 找到首个大于target的值while(left < right){int mid = left + ((right - left) >> 1);if(nums[mid] > target){right = mid; // 找到一个大于target的值,暂存并在左半区间继续查找}else{left = mid + 1; // 没有找到大于target的值,在右半区间继续查找}}return right;}
二、35. 搜索插入位置
1 题目
2 解题思路
(1)由于是排序数组,所以可以使用二分法来进行目标值查找
(2)假设题意是叫你在排序数组中寻找是否存在一个目标值,那么训练有素的读者肯定立马就能想到利用二分法在 O(logn)的时间内找到是否存在目标值。但这题还多了个额外的条件,即如果不存在数组中的时候需要返回按顺序插入的位置,那我们还能用二分法么?答案是可以的,我们只需要稍作修改即可。
考虑这个插入的位置 pos,它成立的条件为:
nums[pos−1]<target≤nums[pos]
其中 nums 代表排序数组。由于如果存在这个目标值,我们返回的索引也是 pos,因此我们可以将两个条件合并得出最后的目标:「在一个有序数组中找第一个大于等于 target的下标」。
问题转化到这里,直接套用二分法即可,即不断用二分法逼近查找第一个大于等于 target的下标 。下文给出的代码是笔者习惯的二分写法,ans 初值设置为数组长度可以省略边界条件的判断,因为存在一种情况是 target 大于数组中的所有数,此时需要插入到数组长度的位置。
3 code
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int n=nums.size();int left=0;int right=n-1;int ans=n;while(left<=right){// >>1相当于/2int mid= left+((right-left)>>1);//移动逻辑if(target<=nums[mid]){ans=mid;right=mid-1;}else{left=mid+1;}}return ans;}
};
三、74. 搜索二维矩阵
1 题目
2 解题思路
(1)由于每行的第一个元素大于前一行的最后一个元素,且每行元素是升序的,所以每行的第一个元素大于前一行的第一个元素,因此矩阵第一列的元素是升序的。
(2)我们可以对矩阵的第一列的元素二分查找,找到最后一个不大于目标值的元素,然后在该元素所在行中二分查找目标值是否存在。
3 code
class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {// 我们可以对矩阵的第一列的元素二分查找,找到最后一个不大于目标值的元素auto row=upper_bound(matrix.begin(),matrix.end(),target,[](const int b, const vector<int>&a){return b<a[0];});if(row==matrix.begin()){return false;}--row;//然后在该元素所在行中二分查找目标值是否存在。return binary_search(row->begin(),row->end(),target);}
};
四、34. 在排序数组中查找元素的第一个和最后一个位置
1 题目
2 解题思路
3 code
class Solution {
private:/*** @brief 返回首个大于target的元素索引,如果不存在,返回数组长度n* @param nums: 输入数组* @param target: 目标值* @return: 目标值索引*/int search(vector<int>& nums, int target){// 二分查找区间[left, right),初始为整个区间int left = 0; int right = nums.size();// 找到首个大于target的值while(left < right){int mid = left + ((right - left) >> 1);if(nums[mid] > target){right = mid; // 找到一个大于target的值,暂存并在左半区间继续查找}else{left = mid + 1; // 没有找到大于target的值,在右半区间继续查找}}return right;}
public:vector<int> searchRange(vector<int>& nums, int target) {// 首个target如果存在,一定是首个大于target-1的元素int start = search(nums, target - 1);if(start == nums.size() || nums[start] != target){return {-1, -1}; // 首个target不存在,即数组中不包含target}// 找到首个大于target的元素,最后一个target一定是其前一位int end = search(nums, target);return {start, end - 1};}
};
五、34. 在排序数组中查找元素的第一个和最后一个位置
1 题目
2 解题思路
这道题要在一个旋转了的有序数组中搜索目标值,要求时间复杂度为 O(logn)。这个时间复杂度只能首先尝试二分查找,但是二分查找的前提是数组有序,这个数组并不满足,还可以用吗?
先别急,虽然这个数组不是整体有序,但它是局部有序的,我们尝试二分着去做看看会发生什么。
二分每次都需要取中点 mid,对于这个旋转的有序数组:
- 如果当前区间 [left, right) 分别在两端有序区间之内,那么就按二分查找去做即可。
- 如果当前区间 [left, right) 是跨越了两端有序子区间的,那么中间点 mid 总会把当前区间 [left, right] 分成两段,一段是有序的,一段是无序的:
(1)如果 nums[mid] > nums[left],肯定是左半区间有序;
(2)如果 nums[mid] < nums[right-1],肯定是右半区间有序;【之所以 -1,是因为 right 初始为数组长度 n,直接取 right 会导致越界】
二分的策略还是一样的,二分的关键是要判断 target 落在哪个区间。我们只能取有序的那个区间来比较,因为只有区间有序,才能 通过端点值的大小比较判断是否落入对应的区间
因此我们只要能够每次判断目标值落到哪个区间,就可以通过二分排除另一半的区间,并不一定要求必须整个数组有序。
3 code
class Solution {
public:int search(vector<int>& nums, int target) {int left = 0; // 二分查找左边界(左闭)int right = nums.size(); // 二分查找右边界(右开)while(left < right){int mid = left + ((right - left) >> 1); if(nums[mid] == target){// 找到目标值,直接返回索引return mid;}if(nums[left] < nums[mid]){// 左半区间有序if(nums[left] <= target && target < nums[mid]){right = mid; // 目标值落入左半区间,更新右边界}else{ left = mid + 1; // 否则在右半区间查找}}else{// 右半区间有序if(nums[mid] < target && target <= nums[right -1]){left = mid + 1; // 目标值落入右半区间,更新左边界}else{right = mid; // 否则在左半区间查找}}}return -1; // 如果退出循环,说明没有找到目标值,返回-1}
};