当前位置: 首页 > article >正文

LeetCode-34. 在排序数组中查找元素的第一个和最后一个位置

1、题目描述:

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

2、代码:

#include <vector>
using namespace std;

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        // 如果数组为空,直接返回 [-1, -1]
        if (nums.empty()) {
            return vector<int>{-1, -1};
        }

        // 调用辅助函数 findFirst 查找目标值的第一个位置
        int start = findFirst(nums, target);

        // 调用辅助函数 findLast 查找目标值的最后一个位置
        int end = findLast(nums, target);

        // 返回结果,包含起始位置和结束位置
        return vector<int>{start, end};
    }

    // 辅助函数:查找目标值的第一个位置
    int findFirst(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1; // 定义左右边界
        int mid;

        // 开始二分查找
        while (l <= r) {
            mid = (l + r) / 2; // 计算中间值

            // 如果找到目标值
            if (target == nums[mid]) {
                // 检查是否是第一个出现的位置
                if (mid == 0 || nums[mid - 1] < target) {
                    return mid; // 确保这是第一个位置,返回索引
                }
                // 如果不是第一个位置,继续向左搜索
                r = mid - 1;
            }
            // 如果目标值大于当前值,说明目标值在右侧,向右搜索
            else if (nums[mid] < target) {
                l = mid + 1;
            }
            // 如果目标值小于当前值,说明目标值在左侧,向左搜索
            else {
                r = mid - 1;
            }
        }

        // 如果循环结束仍未找到目标值,返回 -1
        return -1;
    }

    // 辅助函数:查找目标值的最后一个位置
    int findLast(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1; // 定义左右边界
        int mid;

        // 开始二分查找
        while (l <= r) {
            mid = (l + r) / 2; // 计算中间值

            // 如果找到目标值
            if (target == nums[mid]) {
                // 检查是否是最后一个出现的位置
                if (mid == nums.size() - 1 || nums[mid + 1] > target) {
                    return mid; // 确保这是最后一个位置,返回索引
                }
                // 如果不是最后一个位置,继续向右搜索
                l = mid + 1;
            }
            // 如果目标值大于当前值,说明目标值在右侧,向右搜索
            else if (nums[mid] < target) {
                l = mid + 1;
            }
            // 如果目标值小于当前值,说明目标值在左侧,向左搜索
            else {
                r = mid - 1;
            }
        }

        // 如果循环结束仍未找到目标值,返回 -1
        return -1;
    }
};

3、解题思路:

1. 核心思想

由于数组是有序的,可以利用二分查找的特性:

  • 起始位置 :目标值的第一个出现位置。
  • 结束位置 :目标值的最后一个出现位置。

为了满足时间复杂度 O(logn) 的要求,我们需要分别对这两个位置进行两次独立的二分查找。(也就是说,可以用借助2个辅助函数来实现两个位置的查找

2. 特殊情况处理

  • 空数组 :如果 nums.empty(),直接返回 [-1, -1]
  • 目标值不存在 :如果通过二分查找未找到目标值,返回 [-1, -1]

3. 具体步骤

第一步:查找起始位置
  1. 初始化左右边界 l = 0r = nums.size() - 1
  2. 使用二分查找:
    • 计算中间值 mid = (l + r) / 2
    • 如果 nums[mid] == target
      • 检查是否是第一个出现的位置(即 mid == 0nums[mid - 1] < target)。
      • 如果是,返回 mid
      • 如果不是,说明前面可能还有目标值,继续向左搜索,更新右边界 r = mid - 1
    • 如果 nums[mid] < target,说明目标值在右侧,更新左边界 l = mid + 1
    • 如果 nums[mid] > target,说明目标值在左侧,更新右边界 r = mid - 1
  3. 如果循环结束仍未找到目标值,返回 -1
第二步:查找结束位置
  1. 初始化左右边界 l = 0r = nums.size() - 1
  2. 使用二分查找:
    • 计算中间值 mid = (l + r) / 2
    • 如果 nums[mid] == target
      • 检查是否是最后一个出现的位置(即 mid == nums.size() - 1nums[mid + 1] > target)。
      • 如果是,返回 mid
      • 如果不是,说明后面可能还有目标值,继续向右搜索,更新左边界 l = mid + 1
    • 如果 nums[mid] < target,说明目标值在右侧,更新左边界 l = mid + 1
    • 如果 nums[mid] > target,说明目标值在左侧,更新右边界 r = mid - 1
  3. 如果循环结束仍未找到目标值,返回 -1

4. 时间与空间复杂度分析

  • 每次二分查找的时间复杂度为 O(logn)。
  • 分别查找起始位置和结束位置,总共进行了两次二分查找,总时间复杂度仍为 O(logn)。
  • 算法只使用了常量级别的额外空间(如变量 l, r, mid),因此空间复杂度为 O(1)。

http://www.kler.cn/a/559965.html

相关文章:

  • 什么是拆分金额
  • 容器化部署tomcat
  • 国标28181协议在智联视频超融合平台中的接入方法
  • 【LLM系列6】DPO 训练
  • 算法15--BFS
  • 【数据结构】 最大最小堆实现优先队列 python
  • 新民主主义革命理论的形成依据
  • Maven最新版安装教程
  • IO进程 day05
  • Nmap渗透测试指南:信息收集与安全扫描全攻略(2025最新版本)
  • 独立开发者Product Hunt打榜教程
  • WPF布局控件
  • 【C++】 stack和queue以及模拟实现
  • deepseek 导出导入模型(docker)
  • 计算机毕业设计SpringBoot+Vue.js医院管理系统(源码+文档+PPT+讲解)
  • Git与GitHub:深入理解与高效使用
  • 企业终端遭遇勒索病毒威胁?火绒企业版V2.0--企业用户勒索攻击防护建议!
  • wpf 页面切换的实现方式
  • HarmonyOS Next 计时器组件详解
  • 微信小程序 - 条件渲染(wx:if、hidden)与列表渲染(wx:for)