Skip to content

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

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

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

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

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

解题思路

输入: 一个递增的有序数组 nums,一个目标值 target

输出: 返回目标值在数组中的起始位置和结束位置,若不存在则返回 [-1, -1]。

本题属于边界查找类问题,关键在于利用二分查找精确定位目标值的左右边界。

我们定义一个辅助函数 lower_bound(nums, target),用于查找第一个大于等于 target 的下标(即左边界位置)。

具体步骤如下:

查找左边界
调用 lower_bound(nums, target),获得目标值的起始位置 start。
若 start == nums.length(越界)或 nums[start] !== target,说明目标值不存在,直接返回 [-1, -1]。

查找右边界
调用 lower_bound(nums, target + 1),找到第一个大于目标值的位置,减一即可得到目标值的结束位置 end。

返回结果
返回 [start, end] 即为目标值的起始与结束位置。

代码实现

python
# 查找第一个大于等于 target 的下标(即左边界)
def lower_bound(nums, target):
    left, right = 0, len(nums) - 1
    
    while left <= right:
        mid = left + (right - left) // 2

        if nums[mid] < target:
            # 如果中间值比目标小,说明目标在右半部分
            left = mid + 1
        else:
            # 如果中间值大于等于目标,有可能是答案,缩小右边界
            right = mid - 1
    
    # 最终 left 指向第一个 >= target 的位置
    return left


class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        # 查找 target 的第一个出现位置
        start = lower_bound(nums, target)

        # 如果 start 越界或对应位置不是 target,说明 target 不存在
        if start == len(nums) or nums[start] != target:
            return [-1, -1]

        # 查找 target + 1 的第一个位置,减一得到最后一个 target 的位置
        end = lower_bound(nums, target + 1) - 1

        return [start, end]
javascript
const searchRange = function(nums, target) {
    /**
     * 二分查找:返回第一个大于等于 target 的下标
     * 如果目标存在,这是它的起始位置
     */
    function lowerBound(nums, target) {
        let left = 0;
        let right = nums.length - 1;

        while (left <= right) {
            const mid = Math.floor((left + right) / 2);

            if (nums[mid] >= target) {
                // 当前值 ≥ target,可能是答案,继续往左找更小的
                right = mid - 1;
            } else {
                // 当前值 < target,答案一定在右边
                left = mid + 1;
            }
        }

        // 最终 left 指向第一个 ≥ target 的下标
        return left;
    }

    const start = lowerBound(nums, target);

    // 如果越界,或 nums[start] 不等于目标值,说明不存在
    if (start === nums.length || nums[start] !== target) {
        return [-1, -1];
    }

    // 查找第一个 ≥ target + 1 的位置,减 1 得到最后一个 target 出现的位置
    const end = lowerBound(nums, target + 1) - 1;

    return [start, end];
};

复杂度分析

时间复杂度:O(log n)

空间复杂度:O(1)

链接

34 国际版

34 中文版