网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
+ **`middle = (left + (right - left) / 2 )`**
比较 nums[middle] 的值和 target 的值大小关系
if (nums[middle] > target)
,代表middle向右所有的数字大于**target
**if (nums[middle] < target)
,代表middle向左所有的数字小于**target
**- 既不大于也不小于就是找到了相等的值
nums[middle] = 13 < target = 33,left = middle + 1
见下图:
- 循环条件为
while (left <= right)
- 此时,
left = 6 <= right = 11
,则继续进行循环 - 当前,
middle = left + ((right - left) / 2)
,计算出 middle 的值
计算出 middle 的值后,比较 nums[middle] 和 target 的值,发现:
- nums[middle] = 33 == target = 33,找到目标
🍍错误的写法(错误演示)
对应第一种正向的写法,我们把循环条件修改看看会发生什么事
- 原查找区间 [left, right]
- 原循环条件是 while (left <= right)
修改后题目对应的条件:
- 查找区间不变,仍然是[left, right]
- 查找数字为27 (target = 27)
- 循环条件修改为while (left < right)
代码如下:
class Solution {
public:
int search(vector<int>& nums, int target)
{
int left = 0; //下标从0开始
int right = nums.size() - 1; //定义target在左闭右闭的区间里[left,right] nums.size()求数组中元素个数
// 错误写法
while (left < right) //当left == right时,区间[left, right]仍然有效
{
int mid = left + ((right - left) / 2); // 防止溢出 等同于(left + right)/2;int会向下取整
if (nums[mid] > target)
{
right = mid - 1; // target 在左区间,所以[left, middle - 1]
}
else if (nums[mid] < target) // target 在右区间,所以[middle + 1, right]
{
left = mid + 1;
}
else // nums[middle] == target
{
return mid; // 数组中找到目标值,直接返回下标
}
}
return -1; //未找到目标值
}
};
好了,现在开始用图片模拟过程
- 初始化一个数组,计算 middle 的值
- 根据计算的 middle 值确定 nums[middle]
- 因为nums[middle] = 13 < target = 27,所以left = middle + 1
- 继续计算 middle 的值
- 因为 nums[middle] = 33 > target = 27,所以 right = middle - 1
- 接着计算 middle 的值
- 因为 nums[middle] = 22 < target = 27,此时 left = middle + 1,此时 left = right,而循环条件为while (left < right),所以还未找到27 的情况下算法就跳出了循环,返回 -1
✨写法2:左闭右开
🍎正确的写法(正确演示)
第二种写法:每次查找的区间在 [left, right),(左闭右开区间), 根据区间的定义,条件控制应该如下:
- 循环条件使用 while (left < right)
- if (nums[middle] > target), right = middle,因为当前的 nums[middle] 是大于 target 的,不符合条件,不能取到 middle,并且区间的定义是 [left, right),刚好区间上的定义就取不到 right, 所以 right 赋值为 middle。
代码如下:
class Solution {
public:
int search(vector<int>& nums, int target)
{
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
{
int mid = left + ((right - left) >> 1); // >>右移运算符 向右移动一位相当于除2
if (nums[mid] > target)
{
right = mid; // target 在左区间,在[left, middle)中
}
else if (nums[mid] < target)
{
left = mid + 1; // target 在右区间,在[middle + 1, right)中
}
else // nums[middle] == target
{
return mid; // 数组中找到目标值,直接返回下标
}
}
return -1; // 未找到目标值
}
};
图列解析如下:
第一步是初始化 left 和 right 的值,然后计算 middle 的值
- left = 0, right = size
- 循环条件while (left < right)
因为是左闭右开区间,所以数组定义如下:
- 计算 middle 的值
- 比较 nums[middle] 和 target 的大小:因为 nums[middle] = 22 > target = 3
- 所以 right = middle
- 符合循环的条件,接着计算 middle 的值
- 比较 nums[middle] 和 target 的大小:因为 nums[middle] = 9 > target = 3
- 所以 right = middle
- 符合循环的条件,继续计算 middle 的值
- 比较 nums[middle] 和 target 的大小关系:因为nums[middle] = 0 < target = 3
- 所以 left = middle + 1
- 符合循环条件,接着计算 middle 的值
- 比较 nums[middle] 和 target 的关系:nums[middle] = 3 == target = 3
- 成功找到 target
🍐错误的写法(错误演示)
对应第二种正确的写法,照样把循环条件修改,看会发生什么事
正确的写法中条件为:
- 查找原区间 [left, right)
- 循环条件为 while (left < right)
修改后题目对应的条件:
- 查找区间不变,仍然是 [left, right)
- 循环条件修改为:while (left <= right)
- 查找的数字为26**(数组中不存在这个数字!!!)**
代码:
class Solution {
public:
int search(vector<int>& nums, int target)
{
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
// 错误的写法
while (left <= right) // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
{
int mid = left + ((right - left) >> 1); // >>右移运算符 向右移动一位相当于除2
if (nums[mid] > target)
{
right = mid; // target 在左区间,在[left, middle)中
}
else if (nums[mid] < target)
{
left = mid + 1; // target 在右区间,在[middle + 1, right)中
}
else // nums[middle] == target
{
return mid; // 数组中找到目标值,直接返回下标
}
}
return -1; // 未找到目标值
}
};
**图例详解:**以下是演示全过程:
- 同样,开始初始化一个数组
- 先计算 middle 的值
-
判断 nums[middle] 和 target 的大小关系:nums[middle] = 22 < target = 26
-
left = middle + 1 (其实这里nums[left] 已经等于27,26不可能找到,接下去就看算法是否能够知道数组中不存在26并且返回-1 了)
- 符合循环条件,计算 middle 的值
- 判断 nums[middle] 和 target 的大小关系:nums[middle] = 57 > target = 26
- right = middle
- 满足循环条件,接着计算 middle 的值
- 比较 nums[middle] 和 target 的大小关系:nums[middle] = 33 > target = 26
- right = middle
- 符合循环条件,继续计算 middle 的值
- 比较 nums[middle] 和 target 大小关系,因为 nums[middle] = 27 > target = 26,
- 所以 right = middle,自此,left 和 right 相遇,但是循环条件被我们修改成 while (left <= right) 会接着做循环
- 接下来就是死循环
- 因为 middle = left + ((right - left) / 2),当 left = right 的时候,middle 的值不会继续改变
- middle 不继续改变,由于right = middle,right 也不会改变,所以三个数字自此开始不会继续改变
- 循环条件 while (left <= right) 却仍然满足,不会跳出循环
- 死循环……
✨ 总结
二分法最重要的两个点,就是循环条件和后续的区间赋值问题
因为两者是相互联系,相互影响的,所以就需要两者统一,如果两者不统一,就会出现问题
所以循环条件和赋值问题必须统一,也就是循环不变量。
见到数组的题,我们可以先看一下,有没有顺序,如果有,可以根据题意考虑可以合不合适使用二分查找。
然后再写二分查找时,如何保证你写的二分查找是正确的,可以多想一下这段话
**要对区间定义想情楚,**区间的定义就是不变量,这里建议可以多画画图.
要在二分查找过程中**,保持不变量,就是要用while寻找每一次边界的处理,都要坚持根据区间的定义来操作,这就是循环不变量的规则。**
写二分法,区间定义要想清楚,
一般有两种,左闭右闭 [ left , right ] 左闭右开 [ left , right )
四、常考面试题
💦搜索插入位置
链接:35. 搜索插入位置
class Solution {
public:
int searchInsert(vector<int>& nums, int target)
{
// 双指针
int left = 0,right = nums.size();
int mid = 0;
// 左闭右开
while(left<right)
{
mid = left+((right-left)>>1);
if(nums[mid]>target)
{
right = mid;
}
else if(nums[mid]<target)
{
left = mid+1;
}
else{
return mid;
}
}
return left;
}
};
💦在排序数组中查找元素的第一个和最后一个位置
class Solution {
public:
// 二分查找
int binarysearch(vector<int>& nums, float target)
{
int left = 0;
int right = nums.size();
while(left<right)
{
int mid = left+(right-left)/2;
if(nums[mid]>target)
{
right = mid;
}
else if(nums[mid]<target)
{
left = mid+1;
}
// 这里不能等于 mid
}
// 返回 小于最近整数的前一个位置
return left;
}
vector<int> searchRange(vector<int>& nums, int target)
{
vector<int> v(2,-1);
// 两次二分
int strat = binarysearch(nums,target-0.5);
int end = binarysearch(nums,target+0.5);
// 没找到
if(strat==end)
{
return v;
}
else
{
v[0] = strat;
v[1] = end-1;
}
return v;
}
};
💦x的平方根
链接:69. x 的平方根
class Solution {
public:
int mySqrt(int x)
{
// 1 和 0 的算数平方根为自身
if(x<=1)
{
return x;
}
// 左闭右开 [1,x/2+1)
// 所有大于等于4的数,其算数平方根小于等于它的一半
int left = 1;
int right = x/2+1;
while(left<right)
{
int mid = left+((right-left)>>1);
if((long)mid*mid<x)
{
![img](https://img-blog.csdnimg.cn/img_convert/cfc8e06126ab1c32e920a70ec5e155de.png)
![img](https://img-blog.csdnimg.cn/img_convert/04d487d051e58dcdbf5009a81d506759.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
// 左闭右开 [1,x/2+1)
// 所有大于等于4的数,其算数平方根小于等于它的一半
int left = 1;
int right = x/2+1;
while(left<right)
{
int mid = left+((right-left)>>1);
if((long)mid*mid<x)
{
[外链图片转存中...(img-rMcjrB3z-1715723150335)]
[外链图片转存中...(img-eKYqXeSk-1715723150335)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**