【二分查找-2】34.在排序数组中查找元素的第一个和最后一个位置

题目描述:

给你一个按照非递减顺序排列的整数数组 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]

解题思路:

方法:二分查找

核心思路:

  • 左边界:第一个等于 target 的位置

  • 右边界:最后一个等于 target 的位置

可以分别用两次二分查找:

  1. 找左边界 :用二分查找,找第一个 >= target 的位置

  2. 找右边界 :用二分查找,找第一个 > target 的位置,再减1

具体过程示例:

nums = [5, 7, 7, 8, 8, 10], target = 8

复制代码
找左边界(第一个 >= 8):
left=0, right=5, mid=2, nums[2]=7 < 8 → left=3
left=3, right=5, mid=4, nums[4]=8 >= 8 → right=3
left=3, right=3, mid=3, nums[3]=8 >= 8 → right=2
left=3 > right=2,结束,左边界 = left = 3 ✅

找右边界(第一个 > 8):
left=0, right=5, mid=2, nums[2]=7 <= 8 → left=3
left=3, right=5, mid=4, nums[4]=8 <= 8 → left=5
left=5, right=5, mid=5, nums[5]=10 > 8 → right=4
left=5 > right=4,结束,右边界 = left - 1 = 4 ✅

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left = lowerBound(nums, target);
        int right = upperBound(nums, target) - 1;
        
        // 检查是否找到
        if (left <= right && right < nums.size() && nums[left] == target) {
            return {left, right};
        }
        return {-1, -1};
    }
    
private:
    // 找第一个 >= target 的位置
    int lowerBound(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
    
    // 找第一个 > target 的位置
    int upperBound(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
};

更简洁的写法(用 STL):

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        auto left = lower_bound(nums.begin(), nums.end(), target);
        auto right = upper_bound(nums.begin(), nums.end(), target);
        
        if (left == nums.end() || *left != target) {
            return {-1, -1};
        }
        
        return {(int)(left - nums.begin()), (int)(right - nums.begin() - 1)};
    }
};

复杂度分析:

维度 复杂度 说明
时间复杂度 O(log n) 两次二分查找
空间复杂度 O(1) 只用常数个变量

关键细节:

1. 为什么用 left < right 而不是 left <= right?

这是左闭右开区间的二分模板:

cpp 复制代码
int left = 0, right = nums.size();  // 注意 right 是 size,不是 size-1
while (left < right) {
    int mid = left + (right - left) / 2;
    if (nums[mid] < target) left = mid + 1;
    else right = mid;
}
return left;
  • right 初始为 size,表示开区间

  • 循环结束时 left == right,就是答案

2. 为什么 upperBound 返回 left 后要减1?

upperBound 返回的是第一个 > target 的位置 ,所以右边界是 upperBound - 1。

3. 如何判断是否找到?
cpp 复制代码
if (left <= right && right < nums.size() && nums[left] == target)
  • left <= right:确保区间有效

  • right < nums.size():确保不越界

  • nums[left] == target:确保真的找到了

4. 和「搜索旋转排序数组」的区别
题目 区别
33. 搜索旋转排序数组 旋转数组,找 target
34. 查找第一个和最后一个位置 有序数组,找 target 的边界

总结:

要点 说明
核心思想 两次二分查找:找左边界和右边界
左边界 第一个 >= target 的位置
右边界 第一个 > target 的位置减1
时间复杂度 O(log n)
空间复杂度 O(1)
相关推荐
泯泷1 小时前
为什么 AI 会"失忆"?——读懂 Agent 的记忆系统(一)
人工智能·算法·agent
大侠归来1 小时前
C语言与C++在基础语法上的区别
java·c语言·c++
看看我呀看看我2 小时前
基于Qt的虚拟键盘
c++·qt·qt5·qt6.3
bro_Java6662 小时前
链表进阶2:双向链表的构建
java·数据结构·链表
cu1433 小时前
细谈GM8229的具体功能与其应用
c语言·c++·人工智能·单片机
需要8263 小时前
分布式 ID 生成:雪花算法、号段模式与时钟回拨
分布式·算法
垆边人似月.3 小时前
华为机考题:字符串分隔
算法·华为
CQU_JIAKE3 小时前
9.4【A】
算法
梦帮科技3 小时前
量子张量网络破局大模型:从矩阵乘积态 (MPS) 到张量列 (TT-SVD) 低秩收缩全推导
网络·数据结构·数据库·线性代数·矩阵·架构·模拟退火算法