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

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

知识点:数组,二分查找

题目描述

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

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

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

输入:nums = [], target = 0
输出:[-1,-1]

解法一:二分查找

这是一道典型的二分查找的问题,只不过这次需要我们返回左右边界。
在基础的二分法也就是35题基础上,做些修改。依次找到左右边界。

  • 寻找左边界:有两种情况
  1. 如果整个数组中没有target的值,那最后返回的就是第一个比target大的元素位置的索引;
  2. 如果找到了target的值,不能停,左边界可能还有值,需要将搜索区间移动到mid左边,即right=mid-1;这时候会出现两种情况:
    • 1.在左区间又找到了target。
    • 2.在左区间没有target了。
      要清楚的是最后一次执行的一定是left=right=mid,而且mid左侧都小于target,mid右侧的值都大于等于target,如果判断mid这时候的值也小于target,那left=mid+1,正好就是第一个等于target的值。
  • 寻找右边界:有两种情况
  1. 如果整个数组中没有target的值,那最后返回的就是第一个比target小的位置的索引;
  2. 如果找到了target的值,不能停,右边界可能还有值,需要将搜索区间移动到mid右边,即leftt=mid+1;这时候会出现两种情况:
    • 1.在右区间又找到了target。
    • 2.在右区间没有target了。
      要清楚的是最后一次执行的一定是left=right=mid,而且mid左侧都小于等于target,mid右侧的值都大于target,如果判断mid这时候的值大于target,那right=mid-1,正好就是第一个等于target的值。

总的来说(关键)

  • 左边界其实就是在找第一个>=target的位置
    • 如果数组中有target,返回就是第一个target的位置;
    • 如果数组中无target,返回就是第一个比target大的元素是位置(或者可以理解成要插入target的位置);
  • 右边界其实就是在找最后一个<=target的位置
    • 如果数组中有target,返回就是最后target的位置;
    • 如果数组中无target,返回就是最后一个小于target的位置(或者可以理解成要插入的target的位置的前一个位置)。

所以最后就可以直接比较left和right的位置了,如果left比right还大,那证明不存在了。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int left = leftBound(nums, target);
        int right = rightBound(nums, target);
        while(left > right) return new int[]{-1, -1};
        return new int[] {left, right};
    }
    private int leftBound(int[] nums, int target){
        int left = 0, right = nums.length-1;
        while(left <= right){
            int mid = left + ((right-left) >> 1);
            if(target <= nums[mid]){ //将等于合并过来;
                right = mid-1;
            }else{
                left = mid+1;
            }
        }
        return left; //第一个比大于等于target的索引;
    }
    private int rightBound(int[] nums, int target){
        int left = 0, right = nums.length-1;
        while(left <= right){
            int mid = left + ((right-left) >> 1);
            if(target >= nums[mid]){
                left = mid+1;
            }else{
                right = mid-1;
            }
        }
        return right;
    }
}
  • python
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        left, right = 0, len(nums)-1
        while left <= right:
            mid = left + (right-left) // 2
            if nums[mid] >= target:
                right = mid - 1
            else:
                left = mid + 1
        left_bound = left
        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
        right_bound = right
        if left_bound <= right_bound:
            return [left_bound, right_bound]
        else:
            return [-1,-1]

体会

注意去思考里面的细节,思考左右边界是如何获取到的。要抓住最关键的:最后一次执行的一定是left=mid=right,三个是同一个数,而且mid左侧都比目标值小,mid右侧都比目标值大,这时候就看mid值,如果比t大,那执行right=mid-1;返回left就是正好当前值,当前值比t大,当然也可能包括t;如果比t小,那执行left=mid+1;返回的left移动一位就比t大了。

posted @ 2021-08-04 22:33  Curryxin  阅读(262)  评论(0编辑  收藏  举报
Live2D