[路飞]在排序数组中查找元素的第一个和最后一个位置

154 阅读2分钟

记录 1 道算法题

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

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


要求:提供一个数组,和一个数,返回这个数第一次出现的位置和最后一次出现的位置。这个数在数组里是有重复的,数组是升序的。如果数没出现过就返回 [-1,-1]。

比如:[5,7,7,8,8,10] 和 8,输出:[3,4]

这道题依然是采用二分查找解题。需要输出两个数,所以一定是需要进行两次二分查找。

当我们取中间值的时候,如果是向下取整,那么将会偏向左边。所以我们更接近这个数从左边看取的第一个值。所以取第一个位置的方法和搜索插入位置这道题一样。

如何取值是通过等于进行判断。

    let a = 0
    let b = nums.length - 1
    // 需要将默认值设置为数组的 push 之后的新位置
    let res = nums.length
    
    while(a <= b) {
        const mid = (a + b) >> 1
        const n = nums[mid]
        if (n >= target) {
            // 这里是中间值 大于等于 我们的目标,所以目标在 [a, mid] 中,
            // 所以需要将右边界缩小
            b = mid - 1
            // 右边界缩小的结果就是下次 n 可能不会再大于等于 target,
            // 由于数组是升序的,这里 n 最终会取到 n === target 的位置,
            // 所以这里可以视为 target 匹配上的第一个位置,
            // 区间会不断往左收缩,直到 a b 交叉。
            res = mid
        } else {
            // 这里就是 n < target,中间值 小于 我们的目标,所以目标在[mid,b]中,左边界缩小
            a = mid + 1
        }
    }   

我们通过大于等于号找到了第一个位置。找第一个位置的时候,我们让区间不断往左边收缩,并且保存 target 出现的位置,直到不再出现,最新的位置视为第一个位置。

同样的道理,我们让区间不断往右边收缩,然后记录 target 出现的位置,直到不再出现,那么最新记录的位置就是最后一个位置。

而区间的收缩方向主要通过 等于号判断,因为加上等于的情况,进入某个条件的概率会增加,所以更容易造成边界的缩小。而另一边则是等于的情况实现后,首次进入。

    let a = 0
    let b = nums.length - 1
    // 需要将默认值设置为数组的 push 之后的新位置
    let res = nums.length
    
    while(a <= b) {
        const mid = (a + b) >> 1
        const n = nums[mid]
        if (n > target) {
            // 这时没有等于,所以会在第一次出现比目标值大的数时进入循环,
            // 打个比方就是 [1,2,3,4], 当小于等于 2 都过去之后,[3,4] 在 3 的时候进入这个 if 条件,那么 2 最后出现就是在 3 的前一个位置。
            b = mid - 1
            res = mid
        } else {
            // n <= target,会一直进这个条件,直到区间往右移,等于之后,不再等于
            a = mid + 1
        }
    }

完整代码如下:

    function searchRange(nums, target) {
        let a = 0
        let b = nums.length - 1
        let start = nums.length
        while(a <= b) {
            const mid = (a + b) >> 1
            const n = nums[mid]
            // 大于等于,取第一个位置
            if (n >= target) {
                start = mid
                b = mid - 1
            } else {
                a = mid + 1
            }
        }

        let end = nums.length
        a = 0
        b = nums.length - 1
        while(a <= b) {
            const mid = (a + b) >> 1
            const n = nums[mid]
            // 大于 取最后一个位置的后一个位置
            if (n > target) {
                end = mid
                b = mid - 1
            } else {
                a = mid + 1
            }
        }
        // 还需要判断是否合法,因为有可能数没有不存在,判断下标是否对得上,并且没有交叉。
        // end 需要 - 1,因为是后一个位置
        if (start <= end - 1 && end - 1 < nums.length && nums[start] === target && nums[end - 1] === target) {
            return [start, end - 1]
        }
        return [-1, -1]
    }