LeetCode 👉 HOT 100 👉 在排序数组中查找元素的第一个和最后一个位置 - 中等题

199 阅读2分钟

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

题目

给定一个按照升序排列的整数数组 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[i] 和 目标值 target 相等时,判断是第一次相等,或者是最后一次相等,具体步骤:

  • 定义 ans = [-1, -1]

  • 遍历数组 nums

  • 如果 nums[i] == target

    • 如果这时 ans[0] == -1,说明是第一次相等,将 ans[0] = i

    • 如果 i == n -1 || nums[i + 1] > nums[i],说明已经匹配到最后一个和 target 相等的元素位置,将 ans[1] = i

  • 返回 ans 即为题目所求

代码如下

    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var searchRange = function(nums, target) {
        let ans = [-1,-1];
        const n = nums.length;
        for(let i = 0; i < n; i++) {
            if(nums[i] == target) {
                if(ans[0] == -1) {
                    ans[0] = i;
                }

                if(i == n - 1 || nums[i + 1] > nums[i]) {
                    ans[1] = i;
                }
            }
        }

        return ans;
    };

思考: 上述算法的时间复杂度为 O(n),如果要获得时间复杂度为 O(log n) 的解法,就必然要进行二分了。

上述问题,可以拆解为寻找 第一个等于target的位置第一个大于target的位置减 1

先考虑 第一个等于target的位置,二分查找时,

  • 初始时定义两个指针 left = 0right = n - 1ans = nums.length

  • left < right 时遍历 (注意坑

    • 取中间数 mid = Math.floor((right - left) / 2) + left

    • 如果 nums[mid] > target,进行 right = mid - 1, ans = mid,把区间缩小,把 ans 的可能值也缩小

    • 如果 nums[mid] == target,这时要寻找的是 第一个等于target的位置,所以这个时候,依然需要进行 right = mid - 1, ans = mid,把区间缩小,把 ans 的可能值也缩小

    • 如果 nums[mid] < targte,进行 left = mid + 1

上述遍历结束后,ans 应该为最后一个区间的右边界 right,但是,第一个等于target的位置 会不会出现在左边界 left 上呢?答案是可能的,例如在 [1,2,2,2,3] 查找数字 2 时,ans 应该在左边界上。所以上述代码需要多进行一次比较,循环条件需改为 left <= right


再考虑 第一个大于target的位置,二分查找时,

  • 如果 nums[mid] == target,这时要查找的是比 target 大的数,要做的是 left = mid + 1

其他都相同


最后,需要校验两次寻得的 ans 是否符合题目所求

代码如下

    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var searchRange = function(nums, target) {
        

        let ans = [-1, -1];

        const searchIdx = (nums, target, isLast) => {
            let n = nums.length;

            if(!n) return -1;

            let left = 0, right = n - 1, ans = n;

            while(left <= right) {
                let mid = Math.floor((right - left) / 2) + left;
                if(nums[mid] > target || (isLast && nums[mid] >= target)) {
                    right = mid - 1;
                    ans = mid;
                } else {
                    left = mid + 1;
                }
            }

            return ans;
        }
        
        let leftIdx = searchIdx(nums, target, true);
        let rightIdx = searchIdx(nums, target, false) - 1;

        if(leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] === target && nums[rightIdx] === target) {
            ans = [leftIdx, rightIdx];
        }

        return ans;
    };

小结

二分查找的想法很简单,但是细节需要非常仔细才能想完全,多练才会有正确的思路

LeetCode 👉 HOT 100 👉 在排序数组中查找元素的第一个和最后一个位置 - 中等题

合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍

如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄