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]