目录
[x 的平方根(easy)](#x 的平方根(easy))
[0〜n-1 中缺失的数字(easy)](#0〜n-1 中缺失的数字(easy))
介绍
核心思想:每次都通过比较中间元素,将查找范围缩小一半,直到找到目标或确认目标不存在。
必要条件:
- 数据结构一定要是有序的(升序或降序)
- 支持随机访问(如数组、列表),链表不适合
查找数组中值算法模板

左右边界模板
左边界:

右边界:

如何确定题目到底是用哪个边界的算法呢?
- 可以直接假设mid在我们想找的答案上,然后根据上面的模板和判断条件的要求,看看要缩小那一边的区域。
- 左边界找的是满足判断条件的第一个数,右边界是找满足条件的第二个数
实例
二分查找(easy)

该题目中的数组是升序的,可以考虑用二分查找。
算法流程:
a. 定义 left , right 指针,分别指向数组的左右区间。
b. 找到待查找区间的中间点 mid ,找到之后分三种情况讨论:
- arr[mid] == target 说明正好找到,返回 mid 的值;
- arr[mid] > target 说明 [mid, right] 这段区间都是大于 target 的,因此舍去右边区间,在左边 [left, mid -1] 的区间继续查找,即让 right = mid -1 ,然后重复 2 过程;
- arr[mid] < target 说明 [left, mid] 这段区间的值都是小于 target 的,因此舍去左边区间,在右边 [mid + 1, right] 区间继续查找,即让 left = mid +1 ,然后重复 2 过程;
c. 当 left 与 right 错开时,说明整个区间都没有这个数,返回 -1 。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
while(left<=right)//// 由于两个指针相交时,当前元素还未判断,因此需要取等号
{
int mid=left+(right-left)/2;
if(nums[mid]<target)left=mid+1;
else if (nums[mid]>target)right=mid-1;
else return mid;
}
return -1;
}
};
在排序数组中查找元素的第一个和最后一个(medium)
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

题目解析:在一个升序数组中,找到一段全是目标值的区间,返回全是目标值的第一个下标和最后一个目标值的下标。
假设左边界下标是resleft,目标值为x,右边界下标为resright
寻找左边界思路:
根据左边界进行划分成两个区域:
-
left,resleft-1\]都是小于x的
因此根据mid的落点,我们可以分为两个情况
- 当mid落在[left,resleft-1]区域内是,即nums[mid]<x,那么这个区域内的值是可以全部舍去的,所以让left=mid+1,继续在[mid+1,right]中寻找左边界
- 当mid落在[resleft,right]区域内,即nums[mid]>=x,说明[mid+1,right]可以舍去,但mid位置可能是最终结果,所以right更新成mid,继续在[left,mid]中寻找左边界
寻找右边界思路:
根据右边界进行划分成两个区域:
-
left,resright\]都是小于等于x的
因此根据mid的落点,我们可以分成两种情况:
- 当mid落在[left,resright]区域内是,即nums[mid]<=x,那么[left,mid-1]的值是可以全部舍去的,但mid位置可能是最终结果,所以让left=mid,继续在[mid,right]中寻找右边界
- 当mid落在[resright+1,right]区域内,即nums[mid]>x,说明[mid,right]可以舍去,所以right更新成mid-1,继续在[left,mid-1]中寻找右边界
细节部分:
中间值有两种取法:
mid=left+(right-left)/2 //向下取整
mid=left+(right-left+1)/2//向上取整
当left和right即将相遇时,可能出现死循环:
寻找左边界时:假设left=1,right=2,left=mid+1,right=mid,如果mid=left+(right-left+1)/2,那么mid=2,因为right=mid,导致一直right都没有向前走一步,让left==right让循环结束,所以我们寻找左边界的时候要向下取整。
同理可得右边界
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
if(nums.size()==0)return {-1,-1};
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<target)left=mid+1;
else right=mid;
}
int begin=-1;
if(nums[left]!=target)
return {-1,-1};
else begin=left;
left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left+1)/2;
if(nums[mid]<=target) left=mid;
else right=mid-1;
}
return {begin,right};
}
};
搜索插入位置(easy)

题目解析:在一个升序数组中找到target的下标,如果target不存在就返回它在数组中处在什么下标可以让数组依旧是升序所在的下标。
根据第二个案例,假设mid在下标为1的地方,然后当前数组中的下标为1的数大于target(3>2),判断条件是nums[mid]与target的关系,3>2,说明题意要求的值在左边,所以可以直接舍去右边的值,这时候就是right移动,right有两种移动,一个是right=mid,另一个是right=mid-1。如果选择第二个,那么正确答案就被排除在外了,所以选第一个,第一个在左右边界模板中寻找左边界模板。
解题思路:
假设target所在数组中的下标是i;
那么我们可以把数组分为两个区域:
-
left,i-1\]:全部小于target;
根据这个区域划分,我们可以知道其实这个题相当于找左边界。
因此根据mid的落点,我们可以分为两个情况
-
当mid落在[left,i-1]区域内是,即nums[mid]<x,那么这个区域内的值是可以全部舍去的,所以让left=mid+1,继续在[mid+1,right]中寻找
-
当mid落在[i,right]区域内,即nums[mid]>=x,说明[mid+1,right]可以舍去,但mid位置可能是最终结果,所以right更新成mid,继续在[left,mid]中寻找
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left=0,right=nums.size();
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<target) left=mid+1;
else right=mid;
}
return right;
}
};
x 的平方根(easy)

题目解析:找出x的平方根,即给x开根号,如果x开根号后有小数就只保留整数。
根据第二个案例,假设mid在等于2的地方,然后当前数组中的2*2的数小于target(4<8),找的值在右边,,所以舍去左边的值,这时候就是left移动,left有两种移动,一个是left=mid,另一个是left=mid+1。如果选择第二个,那么正确答案就被排除在外了,所以选第一个,第一个在左右边界模板中寻找右边界模板。
解题思路:假设x的最终平方根结果是index
把区域划分为两部分:
left,index\]的平方根全部都是小于等于x的
\[index+1,right\]的平方根全部大于x
具有二分性
因此根据mid的落点,我们可以分成两种情况:
* 当mid落在\[left,i\]区域内是,即nums\[mid\]\<=x,那么\[left,mid-1\]的值是可以全部舍去的,但mid位置可能是最终结果,所以让left=mid,继续在\[mid,right\]中寻找
* 当mid落在\[i+1,right\]区i域内,即nums\[mid\]\>x,说明\[mid,right\]可以舍去,所以right更新成mid-1,继续在\[left,mid-1\]中寻找
class Solution {
public:
int mySqrt(int x) {
long long left=1,right=x;
if(x==0)return 0;
while(left