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

Leetcode Problem: [34. 在排序数组中查找元素的第一个和最后一个位置] 原题链接


一、题目描述

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

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

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

二、思路

本题分为两种情况:

  • target存在于数组中,但数量若干个。target数量为1就是普通二分搜索法的情况,但是数量>=2,有三种情况:
    • 第一次搜索到的target为正好是第一个位置
    • 第一次搜索到的target为正好是最后一个位置
    • 第一次搜索到的target处在相同元素的中间位置
  • target不存在于数组中,直接返回 [-1,-1]

针对存在若干target的情况,我们在得到找到第一个target之后,可以通过向左和向右缩小区间,来判断这是否是第一个或者最后一个目标元素。

  • 当元素的第一个位置处在第一个找到的target左边,记录first的位置,然后我们要缩小右区间来看在第一个找到的target的左边还有没有目标元素。
  • 同理,当元素的最后一个位置处在第一个找到的target右边,记录last的位置,然后我们要缩小左区间来看在第一个找到的target的右边还有没有目标元素。
  • 如果没有,那么这个target便是始或者终;如果有,那么便更新first或last,直到跳出循环,我们就能得到 [first,last]区间。

针对不存在target的情况,可以通过将first,last初始设置为-1,如果后续没有更新,则自动输出 [-1,-1]表示没有target的位置。

重复元素的存在使得这道题思路会比普通的二分搜索法要绕一点,但理解了还是很简单的。由于本人是纯新手,这道题一上来花了很多时间思考怎么用一个二分来找到左右边界,最后成功把自己绕晕了。

简单来说,就是用两个二分搜索,即先用一个二分找到第一个target,再用第二个二分来看左右两边是否还存在目标元素,若存在就更新first,last位置。

三、代码

Python代码如下(示例):

1. 左闭右闭

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def findlb(nums:List[int],target:int) -> int: # 寻找左区间
            first=-1 # 初始化为-1
            left,right=0,len(nums)-1
            while left<=right: 
                middle=left+(right-left)//2
                if nums[middle]>target:
                    right=middle-1
                elif nums[middle]<target:
                    left=middle+1
                else: 
                    first=middle 
                    #我们找到第一个target了,需要记录
                    right=middle-1 
                    #缩小right来看左边还有没有target,否则这就是第一个
                    #因为此时middle一定是target,右闭缩小right,就必须从前一位开始
            return first # 返回first作为leftboader
        
        def findrb(nums:List[int],target:int) -> int: # 寻找右区间
            last=-1 # 初始化为-1
            left,right=0,len(nums)-1
            while left<=right:
                middle=left+(right-left)//2
                if nums[middle]>target:
                    right=middle-1
                elif nums[middle]<target:
                    left=middle+1
                else:
                    last=middle
                    #我们找到第一个target了,需要记录
                    left=middle+1
                    #缩小left来看右边还有没有target,否则这就是最后一个
                    #因为此时middle一定是target,左闭缩小left,就必须从后一位开始
            return last # 返回last作为rightboader
        first=findlb(nums,target) # 得到最终左区间值
        last=findrb(nums,target) # 得到最终右区间值
        return [first,last] # 返回位置,如果没有,则返回初始值 [-1,-1]

2. 左闭右开

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def findlb(nums: List[int], target: int) -> int:
            first = -1
            left, right = 0, len(nums)
            while left < right:
                middle = left + (right - left) // 2
                if nums[middle] > target:
                    right = middle # 由于左闭右开,因此不减一
                elif nums[middle] < target:
                    left = middle + 1
                else:  
                    first = middle
                    right = middle  
                    # middle=target,由于右开,区间内取不到right的值
                    # 因此相比于左闭右闭,right可以直接等于middle,没必要减一
            return first

        def findrb(nums: List[int], target: int) -> int:
            last = -1
            left, right = 0, len(nums)
            while left < right:
                middle = left + (right - left) // 2
                if nums[middle] > target:
                    right = middle
                elif nums[middle] < target:
                    left = middle + 1
                else:
                    last = middle
                    left = middle + 1 # 左闭不受影响,依旧加一
            return last

        first = findlb(nums, target)
        last = findrb(nums, target)
        return [first, last] # 返回位置,如果没有,则返回初始值 [-1,-1]
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值