🎬 胖咕噜的稞达鸭 :个人主页
🔥 个人专栏 : 《数据结构》《C++初阶高阶》
《Linux系统学习》
《算法入门》
⛺️技术的杠杆,撬动整个世界!

二分查找算法
https://leetcode.cn/problems/binary-search/description/

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1。
你必须编写一个具有 O(log n) 时间复杂度的算法。
解法一:暴力解法->O(N)
从数组下标索引为0访问到数组末尾下标;
解法二:二分查找算法,当数组有二段性的时候就可以二分查找,先排序,拿取到
cpp
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
while(left<=right)
{
//int mid=(left+right)//不建议这样写,会有溢出风险
int mid =(right-left)/2+left;
if(nums[mid]>target)right=mid-1;
else if(nums[mid]<target)left=mid+1;
else return mid;
}
return -1;
}
};
总结朴素二分模板:
go
while(left <= right)//(用于循环结束条件)
{
int mid =left+(right-left)/2;//(求中间点,防止溢出风险)
if(...)
{
left = mid+1;
}
else if(...)
{
right = mid-1;
}
else
return ...
}
排序数组中查找元素的第一个和最后一个位置
\[[https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/](%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE)\]

题目解析
非递减顺序排列的整数数组nums,和一个目标值target,给定目标值在数组中的开始位置和结束位置。
举例:nums = [ 5,7, 7, 8 ,8, 10],target = 8;返回位置【3, 4】
算法原理
解法一:暴力枚举,遍历一遍数组,找到target值,在这个位置标记为begin,向后遍历,找到最后一个符合target ,标记为end,然后返回[begin,end]这段区间的起始位置,这样果不其然就会超时。
解法二:朴素二分查找
定义left在数组索引为0的位置,right在数组最后一个位置。
查找区间左端点:
先查找中间点的位置,mid,如果
mid < target,left = mid + 1;mid >= target,right = mid(不同于朴素二分查找,right不能小于mid)
那么怎么判断循环条件(left < right):
问题:为什么不能加等号?
这里分三种不用进循环的情况:
- 有结果:区间内
left=right的时候,就是最终结果,无需判断; - 区间内部的值都是大于
target,right一定会向左移动,一直移动到left的位置,判断以下,left是否等于target; - 区间内部的值都是小于
target,left向右移动,一直移动到right的位置,判断以下,right是否等于target;
如果判断了就会进入死循环。
求中点的操作:
left+(right-left)/2;left+(right-left+1)/2;
1得到的中点靠左边的位置,2得到的中点靠右边的位置,如果判断到最后,也就是left在right相邻位置,
1会得到mid刚好在left的位置,此时left移动,left=mid+1,也就是落在了right的位置,right=left,此时结束循环的条件;
2会得到mid在right的位置,此时right=mid,并且left<right没有结束循环,
再left+(right-left+1)/2,mid再次落到right的位置,left<right,还是满足循环的条件,mid不停地落在right的位置,一直满足循环的条件,但是mid位置不发生任何变化,结果就是陷入死循环。
查找区间右端点:
- 如果
mid<=target,移动left,left=mid,接着在【left,right】的位置中查找;- 如果
mid >target,移动right,right=mid-1,接着在【left,right】的区间中查找。
处理细节:
循环条件 :必须是left<right;一旦相等就会死循环。
求中点的方式:
left+(right-left)/2;left+(right-left+1)/2;
只能用left+(right-left+1)/2,理由:
如果判断到最后,left跟right相邻的位置,
1求到的mid是left的位置,left=mid,此时left<right,满足循环的条件,不会结束循环,再次求mid,left=mid,依旧满足循环的条件,mid的位置不发生任何变化,结果就是会陷入死循环;
2求到的mid是right的位置,此时right=mid-1,刚好落在了left的位置,right=left,结束循环。
cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
//处理边界情况
if(nums.size() == 0)return {-1,-1};
int begin = 0;
int end=0;
//查找左端点
int 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;
}
//判断是否有结果
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;
}
//判断结果
if(nums[right]!=target)return {-1,-1};
else end = right;
//最后返回最终结果
return {begin,end};
}
};
总结二分模板:
1. 查找左端点的模板:
cpp
while(left<right)
{
int mid = left+(right-left)/2;
if(......)left=mid+1;
else right=mid;
}
2. 查找右端点的模板:
cpp
while(left<right)
{
int mid = left+(right-left+1)/2;
if(......)left=mid;
else right=mid-1;
}
如果硬要选择死记硬背(阴险阴险嘿嘿嘿)可以找规律,当下面出现-1的时候,上面就+1。(右端点)
第一个错误的版本
https://leetcode.cn/problems/first-bad-version/description/

题目解析:
本质上就是查找左端点的问题,左端点到整个数组末尾都是不符合要求的错误版本,由于我们要查找的是
第一个错误版本,所以返回的是左端点的下标。
这里有一个小的细节,在定义左端点left,和右断点right,left不可以从索引为0的位置开始,根据现实情况,如果第一个版本的产品不合格,就一定不会在不合格产品的基础上开发更高版本的产品。由于要计算中间点,所以相应的right=n.
而且如果mid是错误版本,就说明第一个错误版本一定在[1,mid-1]中,right=mid-1;
如果mid不是错误版本,就说明第一个错误版本在[mid+1,n]中,left= mid+1;
cpp
class Solution {
public:
int firstBadVersion(int n) {
int left=1,right=n;
while(left<=right)
{
int mid =left+(right-left)/2;
//如果中间值mid是错误版本,说明【1,mid】中有第一个错误版本
if(isBadVersion(mid))right = mid - 1;
else left = mid+1;//如果中间值mid不是错误版本,说明[mid+1,n]中会有第一个错误版本
}
return left;
}
};
查找x的平方根
https://leetcode.cn/problems/sqrtx/description/

cpp
class Solution {
public:
int mySqrt(int x) {
if(x == 0)return 0;//题目要求是非负整数(正整数和0),如果是0,就直接返回0
int left=1,right=x;
while(left<right)//如果left=right=1,就不用判断了
{
long long mid = left + (right-left+1)/2;//防止溢出,用long long
if(mid * mid <= x)left=mid;
else right = mid-1;
}
return left;
}
};
搜索插入位置
https://leetcode.cn/problems/search-insert-position/

解法一:暴力解法
解法二:二分查找
cpp
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
if(target>nums[right])return right+1;//如果要插入的值比整个数组中的值都大,按照元素顺序返回right+1
while(left<right)
{
int mid = left+(right-left)/2;
if(nums[mid]<target)left=mid+1;
else right = mid;
}
return left;
}
};
山脉数组的封顶索引
https://leetcode.cn/problems/peak-index-in-a-mountain-array/description/

cpp
class Solution {
public:
int peakIndexInMountainArray(vector<int>& arr) {
int left=1,right=arr.size();
while(left<right)
{
int mid=left+(right-left+1)/2;
if(arr[mid]>arr[mid-1])left=mid;
else right=mid-1;
}
return left;
}
};
