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

解析 LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置:二分查找的灵活应用

在算法的学习中,LeetCode 34 题要求在排序数组里查找目标元素的起始和结束位置,且需满足 O(log⁡n)O(\log n)O(logn) 的时间复杂度,这使得二分查找成为解题的关键。以下将详细剖析解题思路、代码逻辑。

一、题目分析

在这里插入图片描述

(一)问题定义

给定非递减排序数组 nums 和目标值 target,找出 target 在数组中的起始索引和结束索引;若不存在,返回 [-1, -1] ,且算法需满足 O(log⁡n)O(\log n)O(logn) 时间复杂度。

(二)核心挑战

利用二分查找高效定位目标值的边界。因数组有序,常规二分可找目标值,但需改造以精准找第一个最后一个出现的位置。

二、原代码思路与问题

(一)思路概述

用一个循环,通过 leftKey 标记查找阶段:

  1. 查找左边界leftKeytrue 时,找到目标值后,更新左边界 topAndDown[0],并收缩右边界(right = mid - 1 ),继续找更左的目标值。
  2. 查找右边界:左边界找到后(left > right 触发 ),重置 leftrightleftKey 设为 false,再次二分,找到目标值后更新右边界 topAndDown[1],并收缩左边界(left = mid + 1 )。

(二)问题

  • 逻辑复杂:通过 left > right 切换查找阶段,增加理解和维护成本,且重置指针易出错。
  • 效率问题:虽整体是 O(log⁡n)O(\log n)O(logn),但代码逻辑不够简洁,可优化为两次独立二分(分别找左、右边界 )。

三、优化后的二分查找思路

(一)两次二分法

  1. 找左边界:在数组中找第一个大于等于 target 的位置,若该位置值不等于 target,则不存在;否则为左边界。
  2. 找右边界:找第一个大于 target 的位置,其前一个位置即为右边界(若存在目标值 )。

(二)实现逻辑

  • 左边界查找:初始化 left=0, right=nums.length,二分过程中,若 nums[mid] < targetleft = mid + 1;否则 right = mid 。最终 left 即为左边界(需验证是否越界及是否等于 target )。
  • 右边界查找:初始化 left=0, right=nums.length,二分过程中,若 nums[mid] <= targetleft = mid + 1;否则 right = mid 。最终 right - 1 即为右边界(需结合左边界验证 )。

四、优化代码实现

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftBound = findLeftBound(nums, target);
        // 左边界不存在,直接返回 [-1, -1]
        if (leftBound == nums.length || nums[leftBound] != target) { 
            return new int[]{-1, -1};
        }
        // 右边界是第一个大于 target 位置的前一个
        int rightBound = findRightBound(nums, target) - 1; 
        return new int[]{leftBound, rightBound};
    }

    // 找第一个大于等于 target 的位置(左边界)
    private int findLeftBound(int[] nums, int target) {
        int left = 0, right = nums.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
    // 找第一个大于 target 的位置(用于推导右边界)
    private int findRightBound(int[] nums, int target) {
        int left = 0, right = nums.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
}

(一)代码流程

  1. 找左边界:调用 findLeftBound,通过二分找到第一个 >= target 的位置。若该位置越界或值不等于 target,直接返回 [-1, -1]
  2. 找右边界:调用 findRightBound 找到第一个 > target 的位置,其前一个位置即为右边界(因数组有序,若存在目标值,右边界是最后一个 target 的索引 )。
  3. 返回结果:组合左、右边界,返回最终结果。

(二)关键逻辑

  • 二分边界处理findLeftBoundfindRightBound 均采用左闭右开区间(right = nums.length ),简化边界条件判断,避免死循环。
  • 结果验证:先验证左边界的有效性(是否为 target ),再推导右边界,确保结果正确。

五、复杂度分析

(一)时间复杂度

两次二分查找,每次时间复杂度为 O(log⁡n)O(\log n)O(logn),总体为 O(log⁡n)O(\log n)O(logn) ,满足题目要求。

(二)空间复杂度

仅使用常数级额外变量,空间复杂度为 O(1)O(1)O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值