34.在排序数组中查找元素的第一个和最后一个位置
灵神b站讲解
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
暴力做法:遍历所有元素,询问是否大于等于8
没有利用数组是有序的这个特性
二分法思路:
- 设定左右指针,随便取个数字,与目标值对比
- 取在中间最合适,如果有奇数个数字,取左边合适下取整,
- 用红色表示小于8,蓝色表示大于8
闭区间写法
- LR分别指向询问的左右边界,即闭区间[L, R]
- M指向当前正在询问的数,
- 如果M<target:
在这里插入代码片
- 标记[L, M]都为红色
- 剩余不确定的区间是[M+1, R]
- 因此下一步是令L等于M+1
- 同时强调了闭区间属性,L-1一定是红色
- 同上,如果M大于等于8,M标记为蓝色
- [M, R]都是蓝色
- 剩余不确定区间是[L, M-1]
- 因此下一步令R等于M-1
- 同时强调了,R+1一定指向了蓝色
- 不断询问:
- 关键点:循环不变量 :
- L-1始终指向红色,
- R+1始终指向蓝色,大于等于目标的数
- 由于循环不变量,R+1就是答案
- 由于循环结束后R+1=L
- 答案也可用L表示
- 如果所有元素都比target小,L不断向后移动,最后等于数组长度
闭区间写法
python
def SearchTarget(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
left = 0
right = n - 1 # 闭区间[left, right]
while left <= right: # 区间不为空
# 不溢出写法:left + (right - left) // 2
mid = (left + right) // 2 # 下取整,取偏左边那个
if nums[mid] < target: # 需要询问的区间缩小到了,mid+1开始
left = mid + 1 # [mid + 1, right]
else: # nums[mid] >= target
right = mid - 1 # [left, mid-1]
return left # 或right + 1 = left片
左闭右开写法
python
def SearchTarget(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
left = 0
right = n # 闭区间[left, right)
while left < right: # 区间不为空,l=r就是空了对于左闭右开
# 不溢出写法:left + (right - left) // 2
mid = (left + right) // 2 # 下取整,取偏左边那个
if nums[mid] < target: # 需要询问的区间缩小到了,mid+1开始
left = mid + 1 # [mid + 1, right]
else: # nums[mid] >= target
right = mid # [left, mid)
return left # 或right = left片
开区间写法
python
def SearchTarget(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
left = -1
right = n # 开区间(left, right)
while left + 1 < right: # 区间不为空,l=r就是空了对于左闭右开
# 不溢出写法:left + (right - left) // 2
mid = (left + right) // 2 # 下取整,取偏左边那个
if nums[mid] < target: # 需要询问的区间缩小到了,mid+1开始
left = mid + 1 # [mid + 1, right]
else: # nums[mid] >= target
right = mid # [left, mid)
return right #
回到本题,要求返回target的开始位置和结束位置。也就是分别是大于等于和小于等于
- 原问题是找出有序数组中第一个大于等于8的数的位置,可以用上面的三种方法找到!
- 假如现在问题是找到大于8的数的位置,可以转化为大于等于8+1的问题
- 如果问题是找到小于x的位置,可以看成是找到大于等于x的那个数的左边的数,也就是(大于等于x)-1的位置
- 如果问题是找到
<=x
的数字位置,实际上是找>x
的左边的位置也就是(>x) - 1
的位置
python
def lower_bound1( nums: List[int], target: int) -> List[int]:
n = len(nums)
left = 0
right = n - 1 # 闭区间[left, right]
while left <= right: # 区间不为空
# 不溢出写法:left + (right - left) // 2
mid = (left + right) // 2 # 下取整,取偏左边那个
if nums[mid] < target:
left = mid + 1 # [mid + 1, right]
else: # nums[mid] >= target
right = mid - 1 # [left, mid]
return left # 或right + 1 = left
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# lower_bound1()找的是>=target的第一个位置
# 本题要找的是左右边界
# 左边界就是lower_bound1(nums, target),就是>=target的位置
start = lower_bound1(nums, target)
if start == len(nums): # 如果start位置超出了数组范围,说明压根没找到一个target!
return [-1, -1]
# 右边界就是找到<=target的位置,如何转换?
# 那就找>target这个值,左边的位置
# 即 lower_bound1(nums, target+1 ) - 1
end = lower_bound1(nums, target + 1) - 1
return [start, end]
________________________________
解答错误
77 / 88 个通过的测试用例
官方题解
输入
nums =
[5,7,7,8,8,10]
target =
6
添加到测试用例
输出
[1,0]
预期结果
[-1,-1]
查看用例,发现问题是nums中根本没有target,这种情况同样返回[-1, -1]
python
def lower_bound1( nums: List[int], target: int) -> List[int]:
n = len(nums)
left = 0
right = n - 1 # 闭区间[left, right]
while left <= right: # 区间不为空
# 不溢出写法:left + (right - left) // 2
mid = (left + right) // 2 # 下取整,取偏左边那个
if nums[mid] < target:
left = mid + 1 # [mid + 1, right]
else: # nums[mid] >= target
right = mid - 1 # [left, mid]
return left # 或right + 1 = left
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# lower_bound1()找的是>=target的第一个位置
# 本题要找的是左右边界
# 左边界就是lower_bound1(nums, target),就是>=target的位置
start = lower_bound1(nums, target)
if start == len(nums) or not target in nums or nums[start] != target: # 如果start位置超出了数组范围,说明压根没找到一个target!
return [-1, -1]
# 右边界就是找到<=target的位置,如何转换?
# 那就找>target这个值,左边的位置
# 即 lower_bound1(nums, target+1 ) - 1
end = lower_bound1(nums, target + 1) - 1
return [start, end]
过了
35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O ( l o g n ) O(log n) O(logn) 的算法。
- 如果这个target比数组最大值都大说明,不存在与这个数组中,应该插入最后一个位置,也就是返回n
- 上面学的三种模板都是找
>=target
的第一个位置,但实际上target不一定存在于nums,怎么办? - 实际上无所谓,就用那段代码就能找到!
闭区间写法
python
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
n = len(nums)
left = 0
right = n - 1 # 闭区间写法[L, R]
while left <= right: # L=R时,mid=L=R,继续循环;如果相等时不循环,可能漏掉这个解
mid = left + (right - left) // 2 # 中间位置或偏左
if nums[mid] < target: # 闭区间,则mid已经不满足
left = mid + 1 # 直接更新L为mid+1, [mid+1, R]
else: # mid >= target, mid可能满足target
right = mid - 1 # 为了求左边界(也就是大于等于的第一个),更新right=mid-1
return left
左闭右开写法
python
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
n = len(nums)
left = 0
right = n # 左闭右开写法[L, R)
while left < right: # L=R时,mid=L=R,继续循环;如果相等时不循环,可能漏掉这个解
mid = left + (right - left) // 2 # 中间位置或偏左
if nums[mid] < target: # 闭区间,则mid已经不满足
left = mid + 1 # 直接更新L为mid+1, [mid+1, R]
else: # mid >= target, mid可能满足target
right = mid # 为了求左边界(也就是大于等于的第一个),更新right=mid-1
return left
开区间写法
错误写法
python
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
n = len(nums)
left = -1
right = n # 开区间写法(L, R),实际[L+1, R-1]
while left < right: # L=R时,mid=L=R,继续循环;如果相等时不循环,可能漏掉这个解
mid = left + (right - left) // 2 # 中间位置或偏左
if nums[mid] < target: # 闭区间,则mid已经不满足
left = mid # 更新L为mid, [mid+1, R]
else: # mid >= target, mid可能满足target
right = mid # 为了求左边界(也就是大于等于的第一个),更新right=mid-1
return left
------------------------------------------------------------------------------------------------------------------------------------
死循环!
------------------------------------------------------------------------------------------------------------------
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
n = len(nums)
left = -1
right = n # 开区间写法(L, R),实际[L+1, R-1]
while left + 1 < right: # L=R时,mid=L=R,继续循环;如果相等时不循环,可能漏掉这个解
mid = left + (right - left) // 2 # 中间位置或偏左
if nums[mid] < target: # 闭区间,则mid已经不满足
left = mid # 更新L为mid, [mid+1, R]
else: # mid >= target, mid可能满足target
right = mid # 为了求左边界(也就是大于等于的第一个),更新right=mid-1
return left+1
------------------------------------------------------------------------------------
通过!
704.二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
- 上道题完美契合简单的二分查找,就算target不在nums也可以计算出应该插入的位置
- 现在,这道题唯一不同只是如果target不在nums,不返回应该插入位置,而是返回-1。简单!
python
def lower_bound(nums, target):
n = len(nums)
left = 0
right = n-1 # 闭区间[left, right]
while left <= right: # 可能存在left==right这个区间,这个区间可能就是target!,如果不加"=",就会忽略这个结果
mid = (left + right) // 2
if mid < target: # mid已经不符合
left = mid + 1 # 直接更新左边界为mid+1即可
else: # 虽然可能mid可能等于right
right = mid - 1 # 更新right为mid-1
return left # 或r-1?
class Solution:
def search(self, nums: List[int], target: int) -> int:
i = lower_bound(nums, target) # 找出目标值应该在的位置,不一定存在
return i if nums[i] == target else -1
____________________________
执行出错
IndexError: list index out of range
~~~~^^^
return i if nums[i] == target else -1
Line 16 in search (Solution.py)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ret = Solution().search(param_1, param_2)
Line 46 in _driver (Solution.py)
_driver()
Line 61 in <module> (Solution.py)
- i有可能是n啊!所以应该加一个逻辑,如果是n也返回-1
无语住了!!!!不是这个错因啊啊啊啊!已经第n次出现这个错误了!!!是nums[mid]
与target
比较!不是mid
与target
比较啊~
好奇怪!!写 return i if i<len(nums) and nums[i] == target else -1
就能过。
写 return i if and nums[i] == target and i<len(nums) else -1
就out list range!!!
为什么!!!
python
def lower_bound(nums, target):
n = len(nums)
left = 0
right = n-1 # 闭区间[left, right]
while left <= right: # 可能存在left==right这个区间,这个区间可能就是target!,如果不加"=",就会忽略这个结果
mid = (left + right) // 2
if nums[mid] < target: # mid已经不符合
left = mid + 1 # 直接更新左边界为mid+1即可
else: # 虽然可能mid可能等于right
right = mid - 1 # 更新right为mid-1
return left # 或r-1?
class Solution:
def search(self, nums: List[int], target: int) -> int:
i = lower_bound(nums, target) # 找出目标值应该在的位置,不一定存在
return i if i<len(nums) and nums[i] == target else -1
好的,以下是简要抓重点说明出现 IndexError
错误的原因:
整体思路
代码想用 lower_bound
函数通过二分查找找到目标值 target
在有序数组 nums
中可能插入的位置 i
,再在 search
函数里判断该位置的值是否为 target
,若是则返回 i
,否则返回 -1。
错误原因
- 在
search
函数的return i if nums[i] == target else -1
这行中,对于测试用例nums = [-1, 0, 3, 5, 9, 12]
,target = 13
时:lower_bound
函数因为13
大于数组nums
中的最大值12
,会返回数组长度6
(即i = 6
),表示若插入13
应在数组末尾下一个位置。- 但当在
search
函数里直接用nums[i]
访问数组元素时,i = 6
超出了数组nums
的有效下标范围(数组下标是0
到len(nums) - 1
),所以就抛出了IndexError: list index out of range
错误。
关键就是没先确认 i
是否在数组有效下标范围内就去访问数组元素,导致访问越界出错。正确做法是像 return i if i < len(nums) and nums[i] == target else -1
这样,先判断 i
的有效性再访问数组元素。