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

92 阅读4分钟

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

题目描述

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

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

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

难度

中等

题目链接

leetcode.cn/problems/fi…

示例

示例 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=3nums[mid]=8,继续向左找
第二次二分左边界[5,7,7,8]L=2, M=2, R=3nums[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=6nums[mid]=8,继续向右找
第二次二分右边界[8,8,10]L=4, M=5, R=6nums[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)

相关题目