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

🎉 关注“青柠代码录公众号,🔥更多精彩内容持续更新中🔥,快来加入我们吧!🚀


LeetCode题目链接:

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


目录

📝 文章摘要

🔍 解题思路

1. 问题分析

2. 算法设计

(1)寻找左边界

(2)寻找右边界

3. 时间复杂度与空间复杂度

✍️ 代码实现

🧩 案例执行过程

案例 1:目标值存在

案例 2:目标值不存在

案例 3:空数组

🖼️ 图示解析

🌈 总结


📝 文章摘要

在本篇中,我们将探讨如何利用二分查找算法解决一个经典的数组问题:找出目标值在非递减数组中的起始和结束位置。我们不仅会详细讲解解题思路,还会通过代码实现、案例分析以及图示帮助读者全方位理解这一问题。


🔍 解题思路

1. 问题分析

我们需要在一个非递减数组中找到目标值 target 的起始和结束位置。如果数组中不存在目标值,则返回 [-1, -1]。根据题目要求,算法的时间复杂度必须为 O(log n),这意味着我们需要使用二分查找来解决问题。

二分查找的核心思想是通过不断缩小区间范围,快速定位目标值的位置。由于我们需要找到目标值的起始和结束位置,因此需要对二分查找进行扩展,分别寻找第一个等于目标值的位置(左边界)和最后一个等于目标值的位置(右边界)。


2. 算法设计

(1)寻找左边界
  • 初始化左右指针 left = 0right = nums.length - 1

  • 在每次循环中计算中间位置 mid = left + (right - left) / 2

  • 如果 nums[mid] >= target,说明目标值可能在左侧或当前位置,因此更新 right = mid - 1

  • 否则,更新 left = mid + 1

  • 循环结束后,检查 left 是否有效(即是否越界且等于目标值),若满足条件,则 left 即为左边界;否则返回 -1

(2)寻找右边界
  • 初始化左右指针 left = 0right = nums.length - 1

  • 在每次循环中计算中间位置 mid = left + (right - left) / 2

  • 如果 nums[mid] <= target,说明目标值可能在右侧或当前位置,因此更新 left = mid + 1

  • 否则,更新 right = mid - 1

  • 循环结束后,检查 right 是否有效(即是否越界且等于目标值),若满足条件,则 right 即为右边界;否则返回 -1


3. 时间复杂度与空间复杂度

  • 时间复杂度:O(log n)。每次二分查找将搜索范围缩小一半,因此时间复杂度为对数级别。

  • 空间复杂度:O(1)。仅使用了常量级别的额外空间。


✍️ 代码实现

以下是完整的 Java 实现代码:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        // 调用辅助函数分别寻找左边界和右边界
        int leftIndex = findLeft(nums, target);
        int rightIndex = findRight(nums, target);
​
        // 返回结果
        return new int[]{leftIndex, rightIndex};
    }
​
    // 寻找左边界
    private int findLeft(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target) {
                right = mid - 1; // 目标值可能在左侧或当前位置
            } else {
                left = mid + 1; // 目标值一定在右侧
            }
        }
        // 检查 left 是否越界且等于目标值
        if (left < nums.length && nums[left] == target) {
            return left;
        }
        return -1;
    }
​
    // 寻找右边界
    private int findRight(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) {
                left = mid + 1; // 目标值可能在右侧或当前位置
            } else {
                right = mid - 1; // 目标值一定在左侧
            }
        }
        // 检查 right 是否越界且等于目标值
        if (right >= 0 && nums[right] == target) {
            return right;
        }
        return -1;
    }
}

🧩 案例执行过程

为了更好地理解算法的执行过程,我们通过几个具体案例进行分析。


案例 1:目标值存在

输入

index:  0  1    2    3  4   5  // 索引位置
nums = [5, 7, 7, 8, 8, 10], target = 8

执行过程

寻找左边界

  • 初始区间:[0, 5]mid = 2nums[mid] = 7,更新 left = 3

  • 当前区间:[3, 5]mid = 4nums[mid] = 8,更新 right = 3

  • 当前区间:[3, 3]mid = 3nums[mid] = 8,更新 right = 2

  • 循环结束,left = 3,检查 nums[3] = 8,左边界为 3

寻找右边界

  • 初始区间:[0, 5]mid = 2nums[mid] = 7,更新 left = 3

  • 当前区间:[3, 5]mid = 4nums[mid] = 8,更新 left = 5

  • 当前区间:[5, 5]mid = 5nums[mid] = 10,更新 right = 4

  • 循环结束,right = 4,检查 nums[4] = 8,右边界为 4

输出

[3, 4]

案例 2:目标值不存在

输入

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

执行过程

寻找左边界

  • 初始区间:[0, 5]mid = 2nums[mid] = 7,更新 left = 3

  • 当前区间:[3, 5]mid = 4nums[mid] = 8,更新 right = 2

  • 循环结束,left = 3,检查 nums[3] != 6,左边界为 -1

寻找右边界

  • 初始区间:[0, 5]mid = 2nums[mid] = 7,更新 left = 3

  • 当前区间:[3, 5]mid = 4nums[mid] = 8,更新 right = 2

  • 循环结束,right = 2,检查 nums[2] != 6,右边界为 -1

输出

[-1, -1]

案例 3:空数组

输入

nums = [], target = 0

执行过程

  • 数组为空,直接返回 [-1, -1]

输出

[-1, -1]

🖼️ 图示解析

以下是对案例 1 的图示解析:

初始数组:[5, 7, 7, 8, 8, 10]
目标值:8
​
寻找左边界:
[5, 7, 7, 8, 8, 10] --> [8, 8, 10] --> [8]
​
寻找右边界:
[5, 7, 7, 8, 8, 10] --> [8, 8, 10] --> [8, 8]

🌈 总结

通过本文的学习,我们掌握了如何利用二分查找高效地解决“找出目标值的起始和结束位置”这一问题。从问题分析到算法设计,再到代码实现和案例执行,每一步都力求清晰易懂。希望本文能为你的学习之路提供帮助!🌟


🎉 感谢阅读!如果你觉得这篇文章对你有帮助,请不要吝啬点赞和分享哦! 🎉

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青柠代码录

您的鼓励是我最大的动力 '◡'

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值