【LeetCode】34. 在排序数组中查找元素的第一个和最后一个位置(同剑指 Offer 53 - I)

一、题目

给定一个按照升序排列的整数数组 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]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

二、解决

1、二分查找法

思路:
排序数组nums中所有数字target形成一个窗口,记窗口的左/右边界索引分别为leftright,分别对应窗口左边/右边的首个元素。

数字target出现次数可以转化为:二分法分别找到左边界left和有边界right,易得数字target的数量为right-left-1
1

算法解析:

  1. 初始化:左边界 i = 0,有边界 j = len(nums)-1
  2. 循环二分:当闭区间[i, j]无元素时跳出:
    2.1. 计算中点m = (i+j)/2(向下取整)
    2.2. 若nums[m]<target,则target在闭区间[m+1, j]中,因此执行 i = m+1
    2.3. 若nums[m]>target,则target在闭区间[i, m-1]中,因此执行 j = m-1
    2.4. 若nums[m]=target,则右边界right在区间[m+1, j]中;左边界left在闭区间[i, m-1]中。因此分为以下两种情况:
    [1]. 若查找右边界right,则执行 i = m+1;(跳出时i指向右边界)
    [2]. 若查找左边界left,则执行j = m-1;(跳出 j 指向左边界)
  3. 返回值。应用两次二分,分别查找rightleft,最终返回right-left-1即可。

代码-版本1:

class Solution {
    
    
    public int search(int[] nums, int target) {
    
    
        // 搜索右边界 right
        int i = 0, j = nums.length - 1;
        while(i <= j) {
    
    
            int m = (i + j) / 2;
            if(nums[m] <= target) i = m + 1;
            else j = m - 1;
        }
        int right = i;
        // 若数组中无 target ,则提前返回
        if(j >= 0 && nums[j] != target) return 0;
        // 搜索左边界 right
        i = 0; j = nums.length - 1;
        while(i <= j) {
    
    
            int m = (i + j) / 2;
            if(nums[m] < target) i = m + 1;
            else j = m - 1;
        }
        int left = j;
        return right - left - 1;
    }
}

时间复杂度: O ( l o g n ) O(logn) O(logn)
空间复杂度: O ( 1 ) O(1) O(1)

上面代码可读性较差,可优化到下面版本:

代码-版本2:

class Solution {
    
    
    public int search(int[] nums, int target) {
    
    
        return helper1(nums, target) - helper2(nums, target) - 1;
    }

    public int helper1(int[] nums, int target) {
    
    
        int left = 0, right = nums.length - 1;
        while (left <= right) {
    
    
            int mid = (left + right) / 2;
            if (nums[mid] <= target) left = mid + 1;
            else right = mid - 1;
        }
        return left;
    }

    public int helper2(int[] nums, int target) {
    
    
        int left = 0, right = nums.length - 1;
        while (left <= right) {
    
    
            int mid = (left + right) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return right;
    }
}

上面代码颇为冗余,可以进一步优化:

代码-版本3:

class Solution {
    
    
    public int search(int[] nums, int target) {
    
    
        return helper(nums, target) - helper(nums, target - 1);
    }
    int helper(int[] nums, int tar) {
    
    
        int i = 0, j = nums.length - 1;
        while(i <= j) {
    
    
            int m = (i + j) / 2;
            if(nums[m] <= tar) i = m + 1;
            else j = m - 1;
        }
        return i;
    }
}

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

三、参考

1、面试题53 - I. 在排序数组中查找数字 I(二分法,清晰图解)

猜你喜欢

转载自blog.csdn.net/HeavenDan/article/details/110920731
今日推荐