二分查找算法(2) _在排序数组中查找元素的第一个和最后一个_模板
个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创二分查找算法(2) _在排序数组中查找元素的第一个和最后一个_模板
收录于专栏【经典算法练习】
本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
1. 题目链接:
2. 题目描述 :
3. 解法 :
解法一:暴力枚举遍历:
算法思路 :
代码展示 :
结果分析 :
解法二: 二分查找 :
算法思路 :
细节处理:
查找区间的左端点
1. 循环条件
2. 求终点的操作
查找区间的右端点
1. 循环条件
2. 求终点的操作
代码展示 :
结果分析 :
4. 二分算法模板总结
1. 题目链接:
OJ链接: 在排序数组中查找元素的第一个和最后一个
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]
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums
是一个非递减数组-109 <= target <= 109
3. 解法 :
解法一:暴力枚举遍历:
算法思路 :
使用两个头尾指针遍历整个数组,在排序数组中查找元素的第一个和最后一个
代码展示 :
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while(left < right)
{
if(nums[left] < target) left++;
else if(nums[right] > target) right--;
else break;
}
if(left <= right && nums[left] == target && nums[right] == target)
return {left, right};
else return{-1, -1};
}
};
结果分析 :
题目顺利通过!时间复杂度为: O(N),不过算法还有进一步优化的空间.
解法二: 二分查找 :
算法思路 :
a.分析插⼊位置左右两侧区间上元素的特点:
设插⼊位置的坐标为 index ,根据插⼊位置的特点可以知道:
•[left, index - 1] 内的所有元素均是⼩于 target 的;
•[index, right] 内的所有元素均是⼤于等于 target 的。
b.设 left 为本轮查询的左边界, right 为本轮查询的右边界。根据 mid 位置元素的信
息,分析下⼀轮查询的区间:
▪ 当 nums[mid] >= target 时,说明 mid 落在了[index, right] 区间上,
mid 左边包括 mid 本⾝,可能是最终结果,所以我们接下来查找的区间在[left,
mid] 上。因此,更新 right 到 mid 位置,继续查找。
▪ 当 nums[mid] < target 时,说明 mid 落在了[left, index - 1] 区间上,
mid 右边但不包括 mid 本⾝,可能是最终结果,所以我们接下来查找的区间在[mid
+ 1, right] 上。因此,更新 left 到 mid + 1 的位置,继续查找。c.直到我们的查找区间的⻓度变为 1 ,也就是 left == right 的时候, left 或者
right 所在的位置就是我们要找的结果。
总结:
查找区间左端点 --- [..., target) [target, ...]
1. nums[mid] < t left = mid + 1
2. nums[mid] >= t right = mid
查找区间右端点 --- [..., target] (target, ...]
1. nums[mid] <= t left = mid
2. nums[mid] > t right = mid - 1
细节处理:
查找区间的左端点
1. 循环条件
while(left < right)
注意这里不能使用while(left <= right)
1. 有结果: 当我们的left == right时,我们的left即是我们的结果,但如果你是使用while(left <= right) 循环,程序会陷入死循环,因为此时right + left / 2 = right/left
2. 全部大于target: 那我们的right会一直往左走,直到left与right相遇,这时候我们只需要判断它们相遇点的值是否等于target就行,相反使用while(left <= right)循环,程序依旧陷入死循环
3. 全部小于target: 那我们的left会一直往右走,与情况2是一样的
2. 求终点的操作
left + (right - left) / 2
注意这里不能使用left + (right - left + 1) / 2
当我们判断只剩下两个元素时,left指向第一个元素, right指向第二个元素
1. mid = left + (right - left) / 2 = left
2. mid = left + (right - left + 1) / 2 = right
当nums[mid] < target
1: left = mid + 1 =right 循环成功结束
2: left = mid + 1 = right + 1成功错过
当nums[mid] >= target
1: right = mid right == left 循环成功结束
2: right = mid 死循环
查找区间的右端点
1. 循环条件
while(left < right)
原理和查找区间的左端点的一样
2. 求终点的操作
left + (right - left + 1) / 2
注意这里不能使用left + (right - left) / 2
当我们判断只剩下两个元素时,left指向第一个元素, right指向第二个元素
1. mid = left + (right - left) / 2 = left
2. mid = left + (right - left + 1) / 2 = right
当nums[mid] <= target
1: left = mid 死循环
2: left = mid left== right + 1 循环成功结束
当nums[mid] > target
1: right = mid - 1 right == left - 1循环成功错过
2: right = mid - 1 right == left 循环成功结束
代码展示 :
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.size() == 0) return {-1, -1};
//找左端点 --- [..., target) [target, ...]
int left = 0, right = nums.size() - 1, begin = 0;
while(left < right)
{
int mid = left + (right - left) / 2;
if(nums[mid] >= target) right = mid;
else left = mid + 1;
}
if(nums[left] == target) begin = left;
else return {-1, -1};
//找右端点 --- [..., target] (target, ...]
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};
}
};
结果分析 :
这里一定要注意细节问题,不然程序很容易死循环
4. 二分算法模板总结
while(left < right)
{
int mid = left + (right - left) / 2;
if(...) left = mid + 1;
else right = mid;
}
while(left < right)
{
int mid = left + (right - left + 1) / 2;
if(...) left = mid;
else right = mid - 1;
}
快速记忆:
分类讨论的代码,就题论题即可
下面出现-1,上面就+1,否侧不加