二分查找专题
性质
- 本质:不是单调性,而是可以找到一个性质,可以把整个区间一分为二,一半满足这个性质,一半不满足 --> 二分就是寻找这个能把区间一分为二的边界
- 思路: 寻找某种特性, 使得整个区间被一分为二, 每次通过确认要找的元素在哪个区间内, 就可以直接排除掉另一半区间
- 有单调性一定可以二分,但是没有单调性也可能可以二分
- 二分时如果不确定边界(比如要不要等于), 可以自己举个实例试一下
二分查找模板
- 模板一
此模板将区间分为[l,mid-1]+[mid,r]
int find(vector<int>& nums){
int l=0,r=nums.size()-1,mid;
while(l<r){
mid=(l+r+1)/2;
if(check(mit)) l=mid;
else r=mid-1;
}
return l;
}
- 模板二
此模板将区间分为[l,mid]+[mid+1,r]
int find(vector<int>& nums){
int l=0,r=nums.size()-1,mid;
while(l<r){
mid=(l+r)/2;
if(check(mid)) l=mid+1;
else r=mid;
}
return l;
}
例题
- 搜索插入位置 https://leetcode.cn/problems/search-insert-position/?envType=study-plan-v2&id=top-100-liked
- 这题思路很简单, 整个区间可以一分为二, 一半小于目标值, 一半大于等于目标值
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l=0,r=nums.size()-1;
int mid;
while(l<r){
mid=(l+r+1)/2;
if(nums[mid]<target) l=mid;
else r=mid-1;
}
if(nums[l]<target) return l+1;
return l;
}
};
- 搜索二维矩阵 https://leetcode.cn/problems/search-a-2d-matrix/?envType=study-plan-v2&id=top-100-liked
- 这题实际上和上一题是一样的, 可以把二维看作一维(进行坐标转换就行)
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m=matrix.size();
int n=matrix[0].size();
int l=0,r=m*n-1,mid;
while(l<r){
mid=(l+r+1)/2;
int x=mid/n,y=mid%n;
cout<<x<<" "<<y<<endl;
if(matrix[x][y]<=target) l=mid;
else r=mid-1;
}
int x=l/n,y=l%n;
cout<<l<<endl;
return matrix[x][y]==target;
}
};
- 在排序数组中查找元素的第一个和最后一个位置 https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/?envType=study-plan-v2&id=top-100-liked
- 这题实际上用了二分查找的两个模板。具体思路:
- 找第一个位置:可以把整个区间分为两部分, 左半部分小于target, 右半部分>=target,找右半部分起点,即target的起点
- 找最后一个位置: 左半区间小于等于target, 右半区间大于target,找左半部分终点
class Solution {
public:
int LeftSearch(vector<int>& nums,int target){
int l=0,r=nums.size()-1,mid;
while(l<r){
mid=(l+r)/2;
if(nums[mid]<target) l=mid+1;
else r=mid;
}
return l;
}
int RightSearch(vector<int>& nums,int target){
int l=0,r=nums.size()-1,mid;
while(l<r){
mid=(l+r+1)/2;
if(nums[mid]<=target) l=mid;
else r=mid-1;
}
return l;
}
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1,-1};
int first_appear=LeftSearch(nums,target);
if(nums[first_appear]!=target) return {-1,-1};
int last_appear=RightSearch(nums,target);
return {first_appear,last_appear};
}
};
- 搜索旋转排序数组 https://leetcode.cn/problems/search-in-rotated-sorted-array/?envType=study-plan-v2&id=top-100-liked
- 解法一:暴力搜索
(这里真的要吐槽一下,遍历就能过吗,这还能打败70%的人吗,什么算法都不用也可以叫中等题吗)
class Solution {
public:
int search(vector<int>& nums, int target) {
for(int i=0;i<nums.size();i++){
if(nums[i]==target) return i;
}
return -1;
}
};
- 解法二:二分
- 由于旋转完的数组一定满足几个性质:整个数组由两个严格递增子数组组成+前一个子数组一定大于后一个子数组, 因此可以二分,每次判断一下mid左边还是右边是严格递增的数组。而通过判断出的这个严格递增数组,通过check一下target在不在它的范围内,就可以得出target应该在这个数组,还是在另一个数组。(因为这个严格递增数组的范围是确定的,通过比较上界和下界就可以确定target在不在其中;而另一个数组不是严格递增,无法确认其范围:最大值可以通过比较该数组的首尾元素确认,但无法确认最小值是多少)
class Solution {
public:
int search(vector<int>& nums, int target) {
int l=0,r=nums.size()-1,mid;
while(l<=r){
mid=(l+r)/2;
if(nums[mid]==target) return mid;
if(nums[l]<=nums[mid]){
if(target>=nums[l]&&target<nums[mid]){
r=mid-1;
}else{
l=mid+1;
}
}else{
if(target>nums[mid]&&target<=nums[r]){
l=mid+1;
}else{
r=mid-1;
}
}
}
return -1;
}
};
- 寻找旋转排序数组中的最小值 https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/?envType=study-plan-v2&id=top-100-liked
- 这题只要找到将区间一分为二的性质,剩下的思路和代码实现就非常简单(虽然思路没想出来)。
首先要明确数组旋转的定义:把末尾元素移到数组头, 即[0,1,2,,n-1]-->[n-1,0,1,2,
,n-2]。因此旋转k次后, 应该得到[n-k+1,n-k,,0,1,2,
,n-k]。因此可以通过最小值m把数组一分为二:[n-k+1,n-k,,m]+[m+1,m+2,
,n-k]。这两个区间满足一个性质: 考虑数组的最后一个元素(n-k), 在最小值左边的元素(不包括最后一个)一定严格小于n-k, 而最小值左边的元素一定严格大于n-k。而每次比较的时候, 若中点值<最后一个元素, 说明这个中点值一定在最小值右边,最小值在左区间。
class Solution {
public:
int findMin(vector<int>& nums) {
int l=0,r=nums.size()-1,mid;
while(l<r){
mid=(l+r)/2;
if(nums[mid]<nums[r]){
r=mid;
}else{
l=mid+1;
}
}
return nums[l];
}
};