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

欢迎关注 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 专栏

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348