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

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

题目描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

难度

中等

题目链接

https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/

示例

示例 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

解题思路

方法:二分查找

这道题可以通过两次二分查找来解决,分别找到目标值的左边界和右边界。

关键点:

  1. 寻找左边界时,即使找到目标值也继续向左搜索
  2. 寻找右边界时,即使找到目标值也继续向右搜索
  3. 两次二分查找的条件略有不同

具体步骤:

  1. 实现一个二分查找函数,通过参数控制查找左边界还是右边界
  2. 分别查找左边界和右边界
  3. 验证找到的边界是否有效

时间复杂度:O(log n)
空间复杂度:O(1)

图解思路

二分查找过程表

步骤 查找类型 数组状态 指针位置 说明
初始状态 左边界 [5,7,7,8,8,8,10] L=0, M=3, R=6 开始查找左边界
第一次二分 左边界 [5,7,7,8,8,8,10] L=0, M=3, R=3 nums[mid]=8,继续向左找
第二次二分 左边界 [5,7,7,8] L=2, M=2, R=3 nums[mid]=7,向右找
第三次二分 左边界 [7,8] L=3, M=3, R=3 找到左边界=3
--- --- --- --- ---
初始状态 右边界 [5,7,7,8,8,8,10] L=0, M=3, R=6 开始查找右边界
第一次二分 右边界 [8,8,8,10] L=3, M=4, R=6 nums[mid]=8,继续向右找
第二次二分 右边界 [8,8,10] L=4, M=5, R=6 nums[mid]=8,继续向右找
第三次二分 右边界 [8,10] L=5, M=5, R=5 找到右边界=5

边界情况分析表

情况 输入示例 预期输出 说明
空数组 [] [-1,-1] 特殊情况处理
单个元素 [8] [0,0] 当target=8时
所有元素相同 [8,8,8] [0,2] 当target=8时
目标不存在 [5,7,9] [-1,-1] 当target=8时
目标在边界 [8,8,9] [0,1] 当target=8时

代码实现

public class Solution {
    public int[] SearchRange(int[] nums, int target) {
        if (nums == null || nums.Length == 0) {
            return new int[] { -1, -1 };
        }
  
        int left = FindBound(nums, target, true);
        int right = FindBound(nums, target, false);
  
        return new int[] { left, right };
    }
  
    private int FindBound(int[] nums, int target, bool isLeft) {
        int left = 0;
        int right = nums.Length - 1;
  
        while (left <= right) {
            int mid = left + (right - left) / 2;
      
            if (nums[mid] == target) {
                if (isLeft) {
                    // 寻找左边界,继续向左搜索
                    if (mid == 0 || nums[mid - 1] != target) {
                        return mid;
                    }
                    right = mid - 1;
                } else {
                    // 寻找右边界,继续向右搜索
                    if (mid == nums.Length - 1 || nums[mid + 1] != target) {
                        return mid;
                    }
                    left = mid + 1;
                }
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
  
        return -1;
    }
}

执行结果

  • 执行用时:76 ms
  • 内存消耗:38.4 MB

代码亮点

  1. 🎯 巧妙使用参数控制左右边界查找
  2. 💡 优化的二分查找实现
  3. 🔍 高效的边界条件处理
  4. 🎨 清晰的代码结构和命名

常见错误分析

  1. 🚫 没有处理数组为空的情况
  2. 🚫 二分查找边界条件写错
  3. 🚫 左右边界查找逻辑混淆
  4. 🚫 返回结果顺序错误

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
暴力搜索 O(n) O(1) 简单直观 不满足题目要求
两次二分查找 O(log n) O(1) 效率最高 实现较复杂
单次二分+线性扫描 O(n) O(1) 实现简单 最坏情况O(n)

相关题目

posted @   旧厂街小江  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示