呀哈喽,我是结衣
提到二分查找你会想到什么,数组一定是有序的?其实不是,我们求解二分查找问题最重要的是找到数组的二段性即可以根据这个性质把数组分为两段的性质,只要找到了这个我们就可以利用二分查找来解决问题。
同时在这篇博客当中,我也会带着大家来找到二分查找的模板,分为,普适性二分模板,查找左边界的二分模板,查找右边界的二分模板
文章目录
1.二分查找(easy)
题目链接:二分查找
题目描述:
思路
初看这道题肯定可以很简单的想到暴力的算法,直接遍历数组来寻找,不过这样就没有利用到数组的有序性。时间复杂度也就是O(N)
可是如果我们利用数组的有序性来解决这道问题我们就可以把时间复杂度降到O(lonN)。
解题方法(利用有序性)
为了利用到数组的有序性,我们下图为例子。
了解了这个我们就可以开始写代码了。
Code
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) right = mid-1;
else if(nums[mid]<target) left = mid+1;
else return mid;
}
return -1;
}
};
二分朴素模板
上面的代码就是利用二分的朴素模板,应用的领域比较小,但是贵在简单,下面我来写一下模板吧
int left = 0,right = nums.size()-1;
while(left<=right)
{
int mid = left+(right-left)/2;
if(.....) right = mid-1;
else if(.....) left = mid+1;
else return mid;
}
差不多就这样,只要会填判断条件里的语句就可以了。
解题方法(利用二段性)
前面和大家讲了只要数组有二段性,我们就可以利用二分查找,可是前面我们没有利用二段性,那么让我们看看什么是二段性吧。
还是同一个数组
为什么我们要这样呢,因为我们知道tar如果存在那么就一定会在左边,所以我们不能让left离开左边,同时我们要让right离开右边所以right等于mid+1**.还要注意的是我们的mid = left+(right-left+1)/2.为什么要加1呢?我们后面一题再讲,还有循环终止的条件变为了left>=right,也下一题再讲**。
Code
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+1)/2;
if(nums[mid]>target) right = mid-1;
else if(nums[mid]<=target) left = mid;
}
if(nums[left]==target) return left;
else return -1;
}
};
2.在排序数组中查找元素的第一个和最后一个位置(medium)
题目链接 :在排序数组中查找元素的第一个和最后一个位置
题目描述:
思路
如果我们利用暴力遍历的话,是可以找到头和尾的。但是同样没有利用到数组的有序性,这题我将带着大家真正理解二段性。
题目让我们查找元素的第一个和最后一个位置,我们可以把题目拆解成两个问题,分别找第一个位置,和最后一个位置。
如果我们要找第一个位置,我们可以将数组分为大于tar的小于等于tar的,这样的话数组就分为了两段。同理如果我们要找最后一个位置我也可以把数组分为小于tar的和大于等于tar的,也是将数组分为了两段。这样我们就可以利用二分查找来解决问题了
解题方法
以示例1为例子来讲解:
我们该任何找到第一和最后的位置呢,以最后下标为例
为了达到这样的目标,mid应该怎么选呢?
最后循环进行的条件又是什么呢?
了解完这么多,我相信大家肯定是可以写出最会一位数的方法即右边界,那么求左边界又又什么不一样呢?不一样的地方有两个
求mid变为mid = left+(right-left)/2,至于为什么就交给大家自己解决咯,原因是一样的。
求left和right,left = mid+1 right = mid。原因也交给大家了
Code
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.size()==0) return {-1,-1};//因为数组可能为空,防止后续导致数组越界
int left = 0,right = nums.size()-1;
while(left<right)//求左边界
{
int mid = left+(right-left)/2;
if(nums[mid]>=target) right = mid;
else left = mid+1;
}
if(nums[left]!=target) return {-1,-1};//如果没找到证明数组中不存在,返回-1,-1
int begin = left;//此时left和right在同一位置,可以随便写。
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};
}
};
查找左右边界的二分模板
在写完上面的代码后我们就可以总结应该左边边界的二分模板,在后序的题目就会方便的多。
这两个模板是差不多的,改动的地方就两个,不过我有个小技巧,如果下面出现了-1那么上面的mid就会有+1.
在后序的题目中我们只要在到了二段性就可以直接敲代码了。
查找左边界的模板
while(left<right)//求左边界
{
int mid = left+(right-left)/2;
if(.......) right = mid;
else left = mid+1;
}
查找右边界的模板
while(left<right)//求右边界
{
int mid = left+(right-left+1)/2;
if(......) left = mid;
else right = mid-1;
}
3.搜索插入位置(easy)
题目链接:搜索插入位置
题目描述:
思路
根据题目的要求我们是可以把数组分成小于等于tar的一段,和大于tar的一段。二段性就出了,那么就利用二分查找吧。
解题方法
了解完使用二分查找,我们还要确定的是一些边界的问题,以免影响后续的操作,当我们tar大于数组最后一个数时,我们可以直接返回nums.size(),或者当tar会小于nums[0]时,我们可以返回0,因为数组是有序的。因为上面我们可以确定我们求的是右边界,可以直接套模板了。
Code
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left= 0,right = nums.size()-1;
if(target>nums[right]) return right+1;
if(target<nums[left]) return 0;
while(left<right)
{
int mid = left+(right-left+1)/2;
if(nums[mid]<=target)left = mid;
else right = mid-1;
}
if(nums[left]==target) return left;
else return left+1;
}
};
4.x的平方根(easy)
题目链接:x的平方根
题目描述:
思路
最简单的就是sqrt(x)
当然肯定是不让用的。同样的我们来找二段性,以x=17为例,当4的平方小于17,5的平方又大于17,那么我们便可求的二段性。
解题方法
找到二段性后,我们直接套模板就可以了。不过这里我们要注意溢出的问题。
Code
class Solution {
public:
int mySqrt(int x) {
long long left = 0,right = x;
while(left<right)
{
long long mid = left+(right-left+1)/2;
if(mid*mid<=x) left = mid;
else right = mid-1;
}
return left;
}
};
5.山脉数组的峰顶索引(medium)
题目链接:山脉数组的峰顶索引
题目描述:
思路
还是找他的二段性
解题方法
找到二段性后,我们就可以又二分查找套模板
Code
class Solution {
public:
int peakIndexInMountainArray(vector<int>& arr) {
int left = 0,right = arr.size()-1;
while(left<right)
{
int mid = left+(right-left+1)/2;
if(arr[mid]>arr[mid-1]) left = mid;
else right = mid-1;
}
return left;
}
};
6.寻找峰值(medium)
题目链接:寻找峰值
题目描述:
思路
你可以和前面一模一样的写,因为题目要求我们只返回一个即可以了,我们完全可把数组缩小成和前题一样的情况,直接把前一题的代码copy一下就可以了(不过要考虑下数组元素小于2的情况)。
解题思路
找到二段性,我们套二分查找的模板
Code
class Solution {
public:
int findPeakElement(vector<int>& nums) {
if(nums.size()<2) return 0;
int left = 0,right = nums.size()-1;
while(left<right)
{
int mid = left+(right-left+1)/2;
if(nums[mid]>nums[mid-1]) left = mid;
else right = mid-1;
}
return left;
}
};
7.寻找旋转排序数组中的最小值(medium)
题目链接:寻找旋转排序数组中的最小值
题目描述:
思路
题目要求我们用O(lonN)的时间复杂度来完成,肯定是想让我们用二分查找,前面也有好多这样的要求,现在我在这提一下吧。本题最重要的还是找到题目的二分性。
解题方法
二分性的寻找,
二段性就找到了,我们要找的是最小数一定是位于右边的,所以我们要不right限制在右边。利用二分查找,套模板
Code
class Solution {
public:
int findMin(vector<int>& nums) {
int left = 0,right = nums.size()-1;
int n = nums.size();
while(left<right)
{
int mid = left+(right-left)/2;
if(nums[mid]<=nums[n-1]) right = mid;
else left = mid+1;
}
return nums[left];
}
};
8.点名(easy)
题目链接:点名
题目描述:
思路
找和下标的对应关系,因为学号为0~n-1,数组的下标也是0 ~n-1。所以我们可以利用这一点来找到二段性。
解题方法
二分查找
我们利用元素和下标的对应性
当然我们这题还有很多的解法,因为题目也没有规定时间复杂度,所以我还要想其他方法
哈希表
我们可以利用哈希表来存储数组元素,如果那个元素没有就说明缺少哪个
高斯求和公式(等差数列求和)
我们可以利用高斯求和先求出总数,任何在减去数组中的元素看差为多少
暴力看下标
正常遍历看谁的下标第一个对不上
位运算
让数组元素与他们的下标进行异或运算,求第一个结果不为0的下标。
以上方法除了二分查找的时间时间复杂度为O(lonN)其他都是O(N).除了二分的代码大家可以自己试试。
Code(二分查找)
class Solution {
public:
int takeAttendance(vector<int>& records) {
if(records[0]!=0)return 0;
int left = 0,right = records.size()-1;
while(left<right)
{
int mid = left+(right-left+1)/2;
if(records[mid]==mid) left = mid;
else right = mid-1;
}
return left+1;
}
};
总结
二分查找算法的关键就是找到数组的二段性,只要找到了二段性我们就可以使用二分查找。
同时我们还可以记住一下二分查找的两模板:普适性二分模板,查找左右边界的二分模板。
二分朴素模板
int left = 0,right = nums.size()-1;
while(left<=right)
{
int mid = left+(right-left)/2;
if(.....) right = mid-1;
else if(.....) left = mid+1;
else return mid;
}
查找左边界的模板
while(left<right)//求左边界
{
int mid = left+(right-left)/2;
if(.......) right = mid;
else left = mid+1;
}
查找右边界的模板
while(left<right)//求右边界
{
int mid = left+(right-left+1)/2;
if(......) left = mid;
else right = mid-1;
}
从这8个题目我们也可以看出,查找左右边界的二分模板更为常见,同时查找左右边界的二分模板也可以解决朴素模板的二问题。但是朴素模板更加简单,使用如果可以使用朴实模板的话还是可以选择他的。然后就是如果我们选择了查找左右边界的二分模板一定要分清用左边界和右边界的区别。
那么本篇关于二分查找算法的内容也是全部讲完了,如果有错误,希望得到您的指正
完