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

本文概览:本文讲解在排序数组中查找元素的第一个和最后一个位置的核心思路:通过两次二分查找分别定位左右边界


一、题目


二、题目分析

这题给的是一个非严格递增 的数组,意味着可能有重复元素,比如 [1, 1, 2, 3] 有两个 1。

题目要求找到 target 的左边界索引右边界索引

  • 查找 1,返回 [0, 1](左边界 0,右边界 1)
  • 查找 2,返回 [2, 2](左右边界都是 2)
  • 查找 4,返回 [-1, -1](不存在)

题目要求时间复杂度 O(log n),看到有序数组 + O(log n),显然要用二分查找


思路概览

Java 实现代码如下

java 复制代码
public int[] searchRange(int[] nums, int target) {
    int[] res = {-1, -1};
    if (nums.length == 0) {
        return res;
    }
    int left = 0;
    int right = nums.length - 1;
    // 二分查找左边界
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    if (left >= nums.length || nums[left] != target) {
        return res;
    }
    res[0] = left;
    // 查找右边界
    right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] > target) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    res[1] = right;
    return res;
}

思路简要说明:

  1. 第一次二分 :找左边界,只要 nums[mid] >= target,就更新 right = mid - 1
  2. 第二次二分 :找右边界,只要 nums[mid] <= target,就更新 left = mid + 1
  3. 边界检查 :如果 left >= nums.lengthnums[left] != target,说明不存在,返回 [-1, -1]

三、思路详解

第一步:为什么不能用"先找到再向两边扩展"

一个直观的思路是:先用二分找到 target 的任意一个位置,然后向两边扩展找到边界。

但这个思路的时间复杂度是 O(log n + k),k 是 target 的个数。最坏情况下(数组全是 target),k = n,时间复杂度退化为 O(n),不符合题目要求。

所以必须用两次二分查找,分别定位左右边界,时间复杂度才是 O(log n)。

第二步:左边界查找的思路

左边界的意思是:第一个等于 target 的位置。

二分查找时,只要 nums[mid] >= target,说明左边界可能在 mid 或 mid 左边,所以更新 right = mid - 1

只有当 nums[mid] < target 时,才更新 left = mid + 1

这样循环结束时,left 就是左边界(如果存在的话)。

第三步:右边界查找的思路

右边界的意思是:最后一个等于 target 的位置。

二分查找时,只要 nums[mid] <= target,说明右边界可能在 mid 或 mid 右边,所以更新 left = mid + 1

只有当 nums[mid] > target 时,才更新 right = mid - 1

这样循环结束时,right 就是右边界(如果存在的话)。

第四步:边界情况处理

有两种情况 target 不存在:

  1. target 比所有元素都小 :比如 [1, 3, 4] 查找 0,循环结束时 left = 0,但 nums[0] = 1 != 0
  2. target 比所有元素都大 :比如 [1, 3, 4] 查找 5,循环结束时 left = 3,但 left >= nums.length
  3. target 在中间但不存在 :比如 [1, 3, 4] 查找 2,循环结束时 left = 1,但 nums[1] = 3 != 2

所以检查条件是:

java 复制代码
if (left >= nums.length || nums[left] != target) {
    return res; // 返回 [-1, -1]
}
第五步:完整执行过程

例子 1:target 存在

nums = [5, 7, 7, 8, 8, 10]target = 8 为例:

第一次二分:找左边界

复制代码
初始:left=0, right=5

第 1 轮:
  mid = 2, nums[2] = 7 < 8
  left = 3

第 2 轮:
  left=3, right=5
  mid = 4, nums[4] = 8 >= 8
  right = 3

第 3 轮:
  left=3, right=3
  mid = 3, nums[3] = 8 >= 8
  right = 2

第 4 轮:
  left=3, right=2
  left > right,退出循环
  left = 3,nums[3] = 8 == 8 ✓
  左边界 = 3

第二次二分:找右边界

复制代码
初始:left=3, right=5

第 1 轮:
  mid = 4, nums[4] = 8 <= 8
  left = 5

第 2 轮:
  left=5, right=5
  mid = 5, nums[5] = 10 > 8
  right = 4

第 3 轮:
  left=5, right=4
  left > right,退出循环
  right = 4
  右边界 = 4

返回 [3, 4]

例子 2:target 不存在

nums = [5, 7, 7, 8, 8, 10]target = 6 为例:

第一次二分:找左边界

复制代码
初始:left=0, right=5

第 1 轮:
  mid = 2, nums[2] = 7 >= 6
  right = 1

第 2 轮:
  left=0, right=1
  mid = 0, nums[0] = 5 < 6
  left = 1

第 3 轮:
  left=1, right=1
  mid = 1, nums[1] = 7 >= 6
  right = 0

第 4 轮:
  left=1, right=0
  left > right,退出循环
  left = 1,nums[1] = 7 != 6
  不存在,返回 [-1, -1]

四、总结

在排序数组中查找元素的第一个和最后一个位置的核心是两次二分查找:

  1. 第一次二分 :找左边界,nums[mid] >= targetright = mid - 1
  2. 第二次二分 :找右边界,nums[mid] <= targetleft = mid + 1
  3. 边界检查:判断 target 是否真的存在

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

相关推荐
其美杰布-富贵-李1 小时前
聚类算法详解:K-means、层次聚类与 DBSCAN
算法·机器学习·kmeans·聚类
天空之外1361 小时前
Docker安装MySQL 8.4 LTS、Docker安装Redis 7.4.x、Docker安装MinIO
java·mysql·docker
fīɡЙtīиɡ ℡1 小时前
深入学习JAVA并发编程(上)
java·开发语言·学习
碧海银沙音频科技研究院2 小时前
4 BES2710编译环境搭建方法
嵌入式硬件·算法
XS0301062 小时前
Spring AI:两种内置 Advisor 快速实现 RAG
java·人工智能·spring
mifengxing2 小时前
LeetCode 238 除自身以外数组的乘积|无除法O(n)时间+O(1)空间双解法
java·算法·leetcode
依然鸣2 小时前
PTA团体程序设计天梯赛L2真题讲解L2-045-048
数据结构·c++·经验分享·学习·算法·pat考试·pat
丢掉幻想准备斗争2 小时前
5.5树与二叉树的应用
算法
ZhouDevin3 小时前
算法论文/模型微调2——仅骨干1%~3% 参数达到与全量微调相当的性能
算法