【优选算法】——二分查找!
目录
1、二分查找
2、在排序数组中查找元素的第一个和最后一个位置
3、搜索插入位置
4、x的平方根
5、山脉数组的封顶索引
6、寻找峰值
7、寻找旋转排序数组中的最小值
8、点名
9、完结散花
1、二分查找
给定一个
n
个元素有序的(升序)整型数组nums
和一个目标值target
,写一个函数搜索nums
中的target
,如果目标值存在返回下标,否则返回-1
。
示例 1:输入:nums
= [-1,0,3,5,9,12],target
= 9 输出: 4 解释: 9 出现在nums
中并且下标为 4示例 2:
输入:nums
= [-1,0,3,5,9,12],target
= 2 输出: -1 解释: 2 不存在nums
中因此返回 -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) return mid;
else if(nums[mid]<target) left=mid+1;
else right=mid-1;
}
return -1;
}
};
2、在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组
nums
,和一个目标值target
。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值
target
,返回[-1, -1]
。你必须设计并实现时间复杂度为
O(log n)
的算法解决此问题。示例 1:
输入:nums = [5,7,7,8,8,10]
, target = 8 输出:[3,4]示例 2:
输入:nums = [5,7,7,8,8,10]
, target = 6 输出:[-1,-1]示例 3:
输入:nums = [], target = 0 输出:[-1,-1]
解题代码:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1,-1};//处理边界情况
vector<int> ret;
int left=0,right=nums.size()-1;
//求左端点
while(left<right)//判断条件一定是<,如果<=则会进入死循环
{
int mid=left+(right-left)/2;//不能加1,否则死循环
if(nums[mid]<target) left=mid+1;
else right=mid;
}
ret.push_back(nums[right]==target?right:-1);
//求右端点
left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left+1)/2;//一定要加1,否则死循环
if(nums[mid]>target) right=mid-1;
else left=mid;
}
ret.push_back(nums[left]==target?left:-1);
return ret;
}
};
3、搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为
O(log n)
的算法。示例 1:
输入: nums = [1,3,5,6], target = 5 输出: 2示例 2:
输入: nums = [1,3,5,6], target = 2 输出: 1示例 3:
输入: nums = [1,3,5,6], target = 7 输出: 4
解题代码:
class Solution {
public:
int searchInsert(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;
else left=mid+1;
}
return nums[left]<target?left+1:left;
}
};
4、x的平方根
给你一个非负整数
x
,计算并返回x
的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如
pow(x, 0.5)
或者x ** 0.5
。示例 1:
输入:x = 4 输出:2示例 2:
输入:x = 8 输出:2 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
解题代码:
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、山脉数组的封顶索引
给定一个长度为
n
的整数 山脉 数组arr
,其中的值递增到一个 峰值元素 然后递减。返回峰值元素的下标。
你必须设计并实现时间复杂度为
O(log(n))
的解决方案。示例 1:
输入:arr = [0,1,0] 输出:1示例 2:
输入:arr = [0,2,1,0] 输出:1示例 3:
输入:arr = [0,10,5,2] 输出:1
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]) right=mid-1;
else left=mid;
}
return left;
}
};
6、寻找峰值
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组
nums
,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。你可以假设
nums[-1] = nums[n] = -∞
。你必须实现时间复杂度为
O(log n)
的算法来解决此问题。示例 1:
输入:nums =[1,2,3,1]
输出:2 解释:3 是峰值元素,你的函数应该返回其索引 2。示例 2:
输入:nums =[
1,2,1,3,5,6,4] 输出:1 或 5 解释:你的函数可以返回索引 1,其峰值元素为 2; 或者返回索引 5, 其峰值元素为 6。
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left=0,right=nums.size()-1;
//求最右端点
while(left<right)
{
int mid=left+(right-left+1)/2;
if(nums[mid]<nums[mid-1]) right=mid-1;
else left=mid;
}
return left;
}
};
7、寻找旋转排序数组中的最小值
已知一个长度为
n
的数组,预先按照升序排列,经由1
到n
次 旋转 后,得到输入数组。例如,原数组nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组
[a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组[a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。给你一个元素值 互不相同 的数组
nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为
O(log n)
的算法解决此问题。示例 1:
输入:nums = [3,4,5,1,2] 输出:1 解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。示例 2:
输入:nums = [4,5,6,7,0,1,2] 输出:0 解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。示例 3:
输入:nums = [11,13,15,17] 输出:11 解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
class Solution {
public:
int findMin(vector<int>& nums) {
int n=nums.size()-1;
//处理特殊情况,即未发生旋转,或旋转到原数组
if(nums[0]<nums[n]) return nums[0];
int left=0,right=n;
//求最左端点
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<nums[0]) right=mid;
else left=mid+1;
}
return nums[left];
}
};
8、点名
某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组
records
。假定仅有一位同学缺席,请返回他的学号。示例 1:
输入: records = [0,1,2,3,5] 输出: 4示例 2:
输入: records = [0, 1, 2, 3, 4, 5, 6, 8] 输出: 7
class Solution {
public:
int takeAttendance(vector<int>& r) {
int end=r.size()-1;
//特殊情况处理
if(r[end]==end) return end+1;
int left=0,right=end;
//求最左端点的二分查找
while(left<right)
{
int mid=left+(right-left)/2;
if(r[mid]>mid) right=mid;
else left=mid+1;
}
return right;
}
};
其他解法:
>hash映射
class Solution {
public:
int takeAttendance(vector<int>& r) {
int n=r.size()+1;
int hash[10000]={0};
for(auto e:r) hash[e]++;
for(int i=0;i<n;i++)
{
if(hash[i]==0)
return i;
}
return n;
}
};
>位运算
class Solution {
public:
int takeAttendance(vector<int>& r) {
int ret=0;
for(int i=0;i<r.size();i++)
{
ret^=r[i];
ret^=i;
}
return ret^=r.size();
}
};
>暴力查找
class Solution {
public:
int takeAttendance(vector<int>& r) {
int ret=0;
for(int i=0;i<r.size();i++)
{
if(r[i]!=i) return i;
}
return r.size();
}
};
>数学(高斯求和公式)
class Solution {
public:
int takeAttendance(vector<int>& r) {
int ret=0,sum1=0,sum2=0;
for(int i=0;i<r.size();i++)
{
sum1+=r[i];
sum2+=i;
}
return sum2-sum1+r.size();
}
};
9、完结散花
好了,这期的分享到这里就结束了~
如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~
如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~
我们下期不见不散~~