34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
查找左边界
初始化 left = 0, right = nums.size() - 1
当 left < right 时循环:
循环结束后,left == right,检查 nums[left] 是否等于 target
查找右边界
初始化 left = 0, right = nums.size() - 1
当 left < right 时循环:
循环结束后,left == right,检查 nums[left] 是否等于 target
以数组 [5, 7, 7, 8, 8, 10],目标值 target = 8 为例:
查找左边界
初始状态:
索引: 0 1 2 3 4 5
数组: [5, 7, 7, 8, 8, 10]
↑ ↑
left right
第一次迭代:
索引: 0 1 2 3 4 5
数组: [5, 7, 7, 8, 8, 10]
↑ ↑
left right
第二次迭代:
索引: 0 1 2 3 4 5
数组: [5, 7, 7, 8, 8, 10]
↑ ↑
left right
第三次迭代:
索引: 0 1 2 3 4 5
数组: [5, 7, 7, 8, 8, 10]
↑
left
right
循环结束:
查找右边界
初始状态:
索引: 0 1 2 3 4 5
数组: [5, 7, 7, 8, 8, 10]
↑ ↑
left right
第一次迭代:
索引: 0 1 2 3 4 5
数组: [5, 7, 7, 8, 8, 10]
↑ ↑
left right
第二次迭代:
索引: 0 1 2 3 4 5
数组: [5, 7, 7, 8, 8, 10]
↑ ↑
left right
第三次迭代:
索引: 0 1 2 3 4 5
数组: [5, 7, 7, 8, 8, 10]
↑
left
right
循环结束:
最终结果
返回 [begin, end] = [3, 4],表示目标值 8 的第一个位置是索引 3,最后一个位置是索引 4。
关键点
class Solution {
public:
vector searchRange(vector& nums, int target) {
if(nums.empty())
{
return {-1,-1};
}
int left = 0;
int right = nums.size()-1;
int begin = -1;
int end =-1;
while(left <= right)
{
int mid = left + (right-left)/2;
if(nums[mid] target)
{
right = mid-1;
}
else
{
left = mid+1;
}
}
if(nums[right] == target)
{
end = right;
}
return {begin,end};
}
};
在查找左边界的时候,right = mid而不是right = mid-1;在查找右边界的时候,left = mid而不是left = mid+1,并且left 为什么要用right = mid而不是right = mid - 1? 为什么我们使用right = mid - 1,就可能错过正确答案 假设数组中有多个相同的目标值,例如[1, 3, 3, 3, 5],目标值target = 3。 当我们找到一个nums[mid] == target时(例如中间的那个3),我们不知道这是不是第一个出现的3。有两种可能: 使用right = mid - 1的问题: 如果当前找到的nums[mid] == target恰好是第一个出现的目标值,那么使用right = mid - 1会将搜索范围缩小到mid左侧,完全排除了mid这个位置。 具体例子: 使用right = mid的优势: 当nums[mid] == target时,使用right = mid: 这样,即使当前的mid就是第一个目标值,我们也不会错过它,因为: 总结:使用right = mid - 1在找到目标值时会完全排除当前位置,如果当前位置恰好是第一个目标值,就会错过正确答案。而right = mid保留了当前位置作为候选答案,同时继续向左搜索可能存在的更早出现的目标值。 为啥查找左端点的mid算法和查找右端点的mid算法不一样 考虑当搜索区间缩小到只有两个元素时,例如 [3, 4]: 使用向上取整的解决方案 使用向上取整 mid = left + (right - left + 1) / 2 可以解决这个问题: 或者,如果 nums[mid] <= target,执行 left = mid = 4,区间变为 [4, 4],循环也会结束。 为什么查找左端点不需要向上取整 关键在于更新策略的不同: 查找左端点时: 查找右端点时: 总结 当使用right = mid和left = mid这种更新方式时,循环条件应该是while(left < right)而不是while(left <= right)。为啥呀 原因如下: 主要原因:避免死循环 如果使用while(left <= right)作为循环条件,当left == right时循环仍会继续执行: 当left == right时,无论使用什么mid计算方式,都会得到mid == left == right 此时: 下一次循环,条件left <= right仍然满足(因为left == right) 区间没有缩小,算法陷入死循环 具体例子 假设数组[1, 3, 5],当搜索区间缩小到[1, 1](即left = right = 1): 如果使用while(left <= right):
正确的写法
class Solution {
public:
vector