一、简介
二分查找算法,也叫折半查找算法。二分查找的思想非常简单,有点类似分治的思想。二分查找针对的是一个有序的数据集合,每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。
二、核心思想
如果中间值大于查找值,则往数组的左边继续查找,如果小于查找值这往右边继续查找。二分查找的思想虽然非常简单,但是查找速度非常长,二分查找的时间复杂度为O(logn)。而且二分查找需要两个必要条件:
- 用于查找的内容逻辑上来说是需要有序的
- 查找的数量只能是一个,而不是多个
三、常规二分查找
在这里我用一道例题来引出:
Leetcode.704 二分查找
- 首先选择数组中间的数字和需要查找的目标值比较
- 如果相等最好,就可以直接返回答案了
- 如果不相等
- 如果中间的数字大于目标值,则中间数字向右的所有数字都大于目标值,全部排除
- 如果中间的数字小于目标值,则中间数字向左的所有数字都小于目标值,全部排除
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0;int right=nums.size()-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]==target){
return mid;
}
else if(nums[mid]<target) left=mid+1;
else right=mid-1;
}
return -1;
}
};
第一种:left和right都是可取的(左闭右闭)
判定条件:left<=right
1.如果中间值大于target:这时right=mid-1
2.如果中间值小于target:这时left=mid+1
public:
int search(vector<int>& nums, int target) {
int left=0;int right=nums.size();
while(left<right){
int mid=left+(right-left)/2;
if(nums[mid]==target) return mid;
else if(nums[mid]<target) left=mid+1;
else right=mid;
}
return -1;
}
};
第二种:left可取,right不可取(左闭右开)
判定条件:left<right
1.如果中间值大于target:这时right=mid
2.如果中间值小于target:这时left=mid+1
当然还有左开右闭的写法,我在这里就不再详述了
四、二分查找拓展
当然直接让你查找值的还是少数,大多数都是以此为基础然后进行一些细微的变化,可能并不是直接让你找这个数值,可能需要你提供大于这个数值或小于这个数值的位置,我在这里提供一个二分查找的拓展(保持在左闭右闭区间内完成书写):
1.中间值等于目标值时,让left=mid+1, 那么left一定在第一个大于目标值的位置,而如果目标值存在,right在等于目标值的位置,如果目标值不存在,那么right在小于目标值的位置
2.中间值等于目标值时,让right=mid-1,那么right一定在第一个小于目标值的位置,而如果目标值存在,left在等于目标值的位置,如果目标值不存在,那么left在大于目标值的位置
tips:运行结束left和right的位置必定相邻,并且left=right+1
Leetcode.278 第一个错误的版本
//把中间值小于等于目标值看做false,大于目标值看成true
//那么一轮循环结束后,很显然left在第一个true的位置
class Solution {
public:
int firstBadVersion(int n) {
int left=1;int right=n;
while(left<=right){
int mid=left+(right-left)/2;
if(!isBadVersion(mid))left=mid+1;
else right=mid-1;
}
return left;
}
};
还有另外一种思路
//把中间值大于等于目标值看做true,小于目标值看成false
//那么一轮循环结束后,很显然right在最后一个false的位置,而left在第一个true的位置
class Solution {
public:
int firstBadVersion(int n) {
int left=1;int right=n;
while(left<=right){
int mid=left+(right-left)/2;
if(isBadVersion(mid))right=mid-1;
else left=mid+1;
}
return left;
}
};
Leetcode.34 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
//查找第一个出现的位置和最后一个出现的位置:查找第一个出现位置,查找最后一个出现位置
int left=0;int right=nums.size()-1;int exist=-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]==target){
exist=mid;
break;
}
else if(nums[mid]<target)left=mid+1;
else right=mid-1;
}
if(exist==-1){
return {-1,-1};
}
//查找第一个出现的位置
left=0;right=nums.size()-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]>=target)right=mid-1;
else left=mid+1;
}
//right在第一个小于target的位置,那么left在大于或等于target的位置
//因为检测到target存在,所以left是第一个等于target的位置
int l=left;
left=0;right=nums.size()-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]<=target)left=mid+1;
else right=mid-1;
}
//同理,right是最后一个等于target的位置
int r=right;
return{l,r};
}
};
Leetcode.436 寻找右区间
class Solution {
public:
vector<int> findRightInterval(vector<vector<int>>& intervals) {
int len=intervals.size();
vector<int>ans(len,0);//保存最终的答案
vector<pair<int,int>>help;//储存每个区间的start
for(int i=0;i<intervals.size();i++){
help.emplace_back(intervals[i][0],i);
}
//1.midVal<=target-->left=mid+1-->left一定是第一个大于val的数,right可能小于或等于val
//2.midVal>=target--->right=mid-1--->right一定是第一个小于val的数,left可能大于或等于val
sort(help.begin(),help.end());
//遍历intervals数组,往ans里填答暗
for(int i=0;i<intervals.size();i++){
int left=0;int right=help.size()-1;
if(intervals[i][1]>help[right].first){
ans[i]=-1;
continue;
}
//需要你找大于等于target的第一个位置,那么等于时移动right,那么right在第一个小于target的位置
//而left在第一个大于等于target的位置
while(left<=right){
int mid=left+(right-left)/2;
if(help[mid].first<intervals[i][1]){
left=mid+1;
}
else right=mid-1;
}
ans[i]=help[left].second;
}
return ans;
}
};