二分查找 闭区间 开区间 半闭半开区间 》转换
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
哪个习惯写哪个,
问题:返回有序数组中第一个≥8的数的位置 如果所有数都<8,返回数组长度
暴力 从左到右 最多on 没有利用数组有序的性质,我们要想尽快的获取到【0,n-1】这个区间内数字与8的关系,最好的方法就是取中间值, 如果列长是偶数,向下取整,(left+right)//2 (java c++ 可以 left+(right-left)//2))
闭区间 lower_round 时间复杂度o logn
L R指针指向的是list的0和n-1
第一次取得中值,位置为M,与target比较 假设小于target,那么说明 M以及小于M位置的数都是小于target的,那么剩下不确定的区间就是【M+1, R】,
所以将L更新成M+1 ,第二次取中值,M更新,假设=target 那么说明【M,R】都满足,不确定的区间就是【L,M-1】,R更新为M-1
继续判断 >=8 R继续更新为M-1 到了L的左边 所有未确定的区间都结束了
最后一次取中值,判断是>=target ,此时R左移在L的左边,二分查找结束,注意此时的结果
L-1始终是红色,也就是<target
R+1始终是蓝色。也就是 >=target 也就是我们需要的答案 还有R+1=L,所以答案可以用L表示
如果查不到比如都比target小 那么L会一直向右移动 L会等于数组长度
def lower_bound(nums,target):
left=0
right=len(nums)-1 #闭区间[left,right]
while left<=right: #区间不为空 这里是<=
mid=left+(right-left)//2
if nums[mid]<target:
left=mid+1 #【mid+1,right】
else:
right=mid-1 #[left,mid-1]
return left
左闭右开
def lower_bound2(nums,target):
left=0
right=len(nums) #左闭右开[left,right)
while left < right: #这里是 <
mid=left+(right-left)//2
if nums[mid]<target:
left=mid+1 #[mid+1 ,right)
else:
right=mid #[left,mid)
return left
全开区间
def lower_bound3(nums,target):
left=-1
right=len(nums) #左闭右开(left,right)
while left +1 < right: #这里是 <
mid=left+(right-left)//2
if nums[mid]<target:
left=mid #(mid+1 ,right)
else:
right=mid #(left,mid)
return right
有序数组中二分查找的四种类型(下面的转换仅适用于数组中都是整数)
问题:返回有序数组中第一个≥8的数的位置 如果所有数都<8,返回数组长度
- 查找第一个大于等于x的下标: low_bound(x)
- 查找第一个大于x的下标:可以转换为
第一个大于等于 x+1 的下标
,low_bound(x+1) - 查找最后一个小于x的下标:可以转换为
第一个大于等于 x 的下标
的左边位置
, low_bound(x) - 1; <X -> (>=X) -1 - 查找最后一个小于等于x的下标:可以转换为
第一个大于等于 x+1 的下标
的左边位置
, low_bound(x+1) -1 <=X -> >=(X+1)-1
理解:先找到 >= target+1 的第一个数,那么它左边那个数就是 <= target 的最后一个数。
上面理解了再来看
做个总结:如果是用上面的lower_round二分 那么其结果就是第一个>=target的左边,通过这个>= 我们可以通过上面转换获得各类输出,下面就是题目
34. 在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
def lower_bound(nums,target):
#ologn
left=0
right=len(nums)-1 #闭区间[left,right]
while left<=right: #区间不为空 这里是<=
mid=left+(right-left)//2
if nums[mid]<target:
left=mid+1 #【mid+1,right】
else:
right=mid-1 #[left,mid-1]
return left
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
start = lower_bound(nums,target)
if start==len(nums) or nums[start]!=target:
return [-1,-1]
end = lower_bound(nums, target+1)-1
return [start,end]
开始位置就是第一个>=target ,就是相当于第一个>=target,所以直接调用lower_bound(nums,target)
结束位置也就是最后一个<=target 将其转换为 (>=target+1)-1 也就是
Lower_round(nums,target+1)-1
作业
2529. 正整数和负整数的最大计数
给你一个按 非递减顺序 排列的数组 nums ,返回正整数数目和负整数数目中的最大值。
• 换句话讲,如果 nums 中正整数的数目是 pos ,而负整数的数目是 neg ,返回 pos 和 neg二者中的最大值。
注意:0 既不是正整数也不是负整数。
def lower_round(nums,target):
left=0
right=len(nums)-1
while left <= right:
mid=left+(right-left)//2
if nums[mid]<target :
left=mid+1
else:
right=mid-1
return left
class Solution:
def maximumCount(self, nums: List[int]) -> int:
last_low_zero=lower_round(nums,0)-1
first_up_zero=lower_round(nums,1)
neg=len( nums[0:last_low_zero+1] )
pos=len( nums[first_up_zero:] )
return max(pos,neg)
- 咒语和药水的成功对数
给你两个正整数数组 spells 和 potions ,长度分别为 n 和 m ,其中 spells[i] 表示第 i 个咒语的能量强度,potions[j] 表示第 j 瓶药水的能量强度。
同时给你一个整数 success 。一个咒语和药水的能量强度 相乘 如果 大于等于 success ,那么它们视为一对 成功 的组合。
请你返回一个长度为 n 的整数数组 pairs,其中 pairs[i] 是能跟第 i 个咒语成功组合的 药水 数目。
def lower_round(nums,target):
left=0
right=len(nums)-1
while left <= right:
mid=left+(right-left)//2
if nums[mid]<target :
left=mid+1
else:
right=mid-1
return left
class Solution:
def successfulPairs(self, spells: List[int], potions: List[int], success: int) -> List[int]:
m=len(potions)
potions.sort()
return [ m - lower_round(potions, success/x) for x in spells]
补充bisect_left()
bisect()和bisect_right()等同,那下面就介绍bisect_left()和bisec_right()的区别!
用法:
index1 = bisect(ls, x) #第1个参数是列表,第2个参数是要查找的数,返回值为索引
index2 = bisect_left(ls, x)
index3 = bisec_right(ls, x)
bisect.bisect和bisect.bisect_right返回大于x的第一个下标(相当于C++中的upper_bound),bisect.bisect_left返回大于等于x的第一个下标(相当于C++中的lower_bound)。
2.1 bisect_left()
bisect.bisect_left(a, x, [lo=0, hi=len(a)]):
在序列 a 中二分查找适合元素 x 插入的位置,保证 a 仍为 有序序列。
若序列 a 中存在与 x 相同的元素,则返回与 x 相同的第一个 (最左侧) 元素的位置索引,使得 x 若插入后能位于其 左侧;
若序列 a 中不存在与 x 相同的元素,则返回与 x 右侧距离最近的元素位置索引,使得 x 若插入后能位于其 左侧。
而 lo 和 hi 为可选参数,分别定义查找范围/返回索引的 上限和下限,缺省时默认对 整个 序列查找。
因此,返回的位置索引 又称为 “插入点”,将其设为 i,则序列 a 可以被划分为两部分,切片表示左侧 a[lo, i) 和右侧 a[i, hi] 。
bisect.bisect_right(a, x, lo=0, hi=len(a), *, key=None)
bisect.bisect(a, x, lo=0, hi=len(a), *, key=None)
类似于 bisect_left(),但是返回的插入点是在 a 中任何现有条目 x 之后(即其右侧)。
返回的插入点 ip 将数组 a 分为两个切片使得对于左侧切片 all(elem <= x for elem in a[lo : ip]) 为真值而对于右则切片 all(elem > x for elem in a[ip : hi]) 为真值。
在 3.10 版本发生变更: 增加了 key 形参。