美文网首页leetcode
[中等] 34. 在排序数组中查找元素的第一个和最后一个位置

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

作者: 章光辉_数据 | 来源:发表于2020-06-12 22:07 被阅读0次

    欢迎关注 leetcode 专栏

    题目

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

    你的算法时间复杂度必须是 O(log n) 级别。

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

    示例 1:

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

    示例 2:

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

    链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array

    解法

    常规解法

    思路:把一个问题拆分成两个子问题:找左边界 + 找右边界,解法都是一样的,只是循环终止条件略有差异。

    对于左边界,满足以下条件时认为找到了:

    1. 值与 target 相同
    2. 左边没有其他值(即索引为0)或者左边的值比 target 小

    对于右边界,满足以下条件时认为找到了:

    1. 值与 target 相同
    2. 右边没有其他值(即索引为总长度 - 1)或者右边的值比 target 大
    # 常规解法
    class Solution:
        def searchRange(self, nums: int, target: int) -> int:
            if not nums: return [-1, 1]
            return [self.find_left_range(nums, target), self.find_right_range(nums, target)]
    
        def find_left_range(self, nums, target):        
            left, right = 0, len(nums) - 1
            while left <= right:
                middle = left + (right - left) // 2
                if nums[middle] < target:
                    left = middle + 1
                else:
                    if nums[middle] == target and (middle == 0 or nums[middle - 1] < target):
                        return middle
                    right = middle - 1
            return -1  
    
        def find_right_range(self, nums, target):
            left, right = 0, len(nums) - 1
            while left <= right:
                middle = left + (right - left) // 2
                if nums[middle] > target:
                    right = middle - 1
                else:
                    if nums[middle] == target and (middle == len(nums) - 1 or nums[middle + 1] > target):
                        return middle
                    left = middle + 1
            return -1 
    

    空间复杂度 O(1),只是额外申请了几个变量的空间。
    时间复杂度 O(logn),就是二分查找的复杂度。

    可以看出,查找左右边界的过程是分离的,所以也有优化的空间,也就是复用一部分结果。比如先用二分查找法,在找到任意一个目标值时,对于此时的 left、middle、right ,分别使用 [left, middle] 作为查找左边界的初始值,使用 [middle, right] 作为查找右边界的初始值。不过,这种操作的复杂度没变,时间和空间上并没有可见的改善。

    递归解法

    递归解法与常规解法相比而言,有一个容易犯错的地方,就是当 nums[middle] == target 时,下一个递归的初始值要如何选择。

    1. 当查找左边界时,如果 nums[middle] == target ,那么 middle 只能是新的临时右边界初始值;
    2. 当查找右边界时,如果 nums[middle] == target ,那么 middle 只能是新的临时左边界初始值;
    # 递归解法
    class Solution:
        def searchRange(self, nums: int, target: int) -> int:
            if not nums: return [-1, -1]
            return [self.find_left_range(nums, target, 0, len(nums) - 1), 
                    self.find_right_range(nums, target, 0, len(nums) - 1)]
    
        def find_left_range(self, nums, target, left, right):
            if left > right: return -1
            middle = left + (right - left) // 2
            if nums[middle] == target and (middle == 0 or nums[middle - 1] < target):
                return middle
            elif nums[middle] < target:
                return self.find_left_range(nums, target, middle + 1, right)
            else:
                return self.find_left_range(nums, target, left, middle - 1)
    
        def find_right_range(self, nums, target, left, right):
            if left > right: return -1
            middle = left + (right - left) // 2
            if nums[middle] == target and (middle == len(nums) - 1 or nums[middle + 1] > target):
                return middle
            elif nums[middle] <= target:
                return self.find_right_range(nums, target, middle + 1, right)
            else:
                return self.find_right_range(nums, target, left, middle - 1)
    
    

    空间复杂度 O(logn),最差情况下递归的深度就是logn。
    时间复杂度 O(logn),不变。

    Python专属解法

    python 的内置库 bisect ,可以让我们很方便地对有序列表进行操作。

    这里主要使用两个函数:

    1. bisect_left(nums, x):对于 x,找到在有序数组 nums 的合适插入位置(如果数组里有多个 x 则返回最左侧的插入位置,即第一个 x 的索引),以保持有序。
    2. bisect_right(nums, x):对于 x,找到有序数组 nums 的合适插入位置(如果数组里有多个 x 则返回最右侧的插入位置,即最后一个 x 的索引 + 1),以保持有序。
    import bisect
    
    class Solution:
        def searchRange(self, nums: int, target: int) -> int:
            if not nums: return [-1, -1]
    
            left = bisect.bisect_left(nums, target)
            right = bisect.bisect_right(nums, target)
            if left == len(nums) or nums[left] != target:
                return [-1, -1]
            else:
                return [left, right - 1]
    
    

    还没看过 bisect 库的源码,但一般 python 内置库为了满足各种场景的需要,会额外加入一些异常处理逻辑,因此处理时间也会更长一些。

    更多刷题,尽在 leetcode 专栏

    相关文章

      网友评论

        本文标题:[中等] 34. 在排序数组中查找元素的第一个和最后一个位置

        本文链接:https://www.haomeiwen.com/subject/ozsntktx.html