二分查找法总结
二分查找法总结
循环不变式
初始化:它在循环的第一轮迭代开始之前,应该是正确的。
保持:如果在某一次循环迭代开始之前是正确的,那么在下一次迭代开始之前,它也应该保持正确(假设当循环变量等于k时符合,再看执行一遍循环体后是否还符合循环不变式)。
终止:循环能够终止,并且可以得到期望的结果。(这一步是和数学归纳法不同的一点,用循环不变式则更进一步,数学归纳法到这里就得出了一个关系式就结束,而用循环不变式,不但要先确保一个正确的关系式,还要看最后循环结束时,循环变量最后等于多少,根据循环不变式推导是否符合自己的要求。)。
只要保障上述三者成立,那么这个循环就是正确的。下列问题全部是在输入数组是升序的情况下讨论。
准确查找问题
区间为[a, b)类型。
初始化:我们假设都给定了正确类型的nums和target。
保持:当nums[mid] == target的时候,显然我们找到了target,我们直接返回mid即可。当target > nums[mid]的时候,此时[l, mid]中的元素全部小于target,此时target只会存在于[mid+1, r)这个区间中。当target < nums[mid]的时候,此时[mid, r)这个区间内的元素全部大于target,此时target只会存在于[l, mid-1]区间内,此时为了保障区间类型的统一,我们将[l, mid-1]变成[l, mid)。
终止:当l==r,此时区间[l, r)中没有元素了,那么target就不在nums中。
class Solution:
def find(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
l, r = 0, len(nums)
while l < r:
mid = (l + r)//2
if target == nums[mid]:
return mid
elif target > nums[mid]:
l = mid + 1
else:
r = mid
return -1
区间为[a, b]类型
初始化:我们假设都给定了正确类型的nums和target。
保持:当nums[mid] == target的时候,显然我们找到了target,我们直接返回mid即可。当target > nums[mid]的时候,此时[l, mid]中的元素全部小于target,此时target只会存在于[mid+1, r]这个区间中。当target < nums[mid]的时候,此时[mid, r]这个区间内的元素全部大于target,此时target只会存在于[l, mid-1]区间内。
终止:当l > r,此时区间[l, r]中没有元素了,那么target就不在nums中。
class Solution:
def find(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
l, r = 0, len(nums) - 1
while l <= r:
mid = (l + r)//2
if target == nums[mid]:
return mid
elif target > nums[mid]:
l = mid + 1
else:
r = mid - 1
return -1
有序(重复)数组中查找第一个指定元素和最后一个指定元素的问题
在一个有序数组arr中, 寻找大于等于target的元素的第一个索引,如果存在, 则返回相应的索引index,否则, 返回arr的元素个数 n。
保持:当nums[mid] == target的时候,显然我们找到了target,但是我们需要找的是第一个大于等于target的元素,所以我们需要在[l, mid-1]中继续寻找。当target > nums[mid]的时候,此时[l, mid]中的元素全部小于target,此时大于等于target的元素只会存在于[mid+1, r]这个区间中。当target < nums[mid]的时候,此时[mid, r]这个区间内的元素一定大于target,此时target只会存在于[l, mid-1]这个区间内。
终止:当l>r,此时区间[l, r]中没有元素了,而l是大于等于target的第一个位置(很容易分析,假设l==r的时候target == nums[mid],此时r=mid-1而l=mid)。
class Solution:
def lower_bound(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
l, r = 0, len(nums)-1
while l <= r:
mid = (l + r)//2
if target <= nums[mid]:
r = mid - 1
else:
l = mid + 1
return l
在一个有序数组arr中, 寻找大于target的元素的第一个索引,如果存在, 则返回相应的索引index,否则, 返回arr的元素个数 n。
初始化:我们假设都给定了正确类型的nums和target。
保持:当nums[mid] == target的时候,显然我们找到了target,但是我们需要找的是第一个大于target的元素,所以我们需要在[mid+1, r]中继续寻找。当target > nums[mid]的时候,此时[l, mid]中的元素全部小于target,此时大于等于target的元素只会存在于[mid+1, r]这个区间中。当target < nums[mid]的时候,此时[mid, r]这个区间内的元素一定大于target,此时target只会存在于[l, mid]这个区间内。
终止:当l>r,此时区间[l, r]中没有元素了,而l是大于target的第一个位置(分析同上)。
class Solution:
def upper_bound(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
l, r = 0, len(nums)-1
while l <= r:
mid = (l + r)//2
if target >= nums[mid]:
l = mid + 1
else:
r = mid - 1
return l
前一个元素和后一个元素问题
如果找到target,返回第一个target相应的索引index;如果没有找到target, 返回比target小的最大值相应的索引, 如果这个最大值有多个, 返回最大索引;如果这个target比整个数组的最小元素值还要小, 则不存在这个target的floor值, 返回-1。
其实这就是一个lower_bound问题,我们最后只要判断target==nums[l](需要保证l<len(nums)),如果成立返回l,否则返回l-1。
class Solution:
def floor(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
l, r = 0, len(nums)-1
while l <= r:
mid = (l + r)//2
if target <= nums[mid]:
r = mid - 1
else:
l = mid + 1
if l < len(nums) and nums[l] == target:
return l
return l-1
如果找到target,返回最后一个target相应的索引index;如果没有找到target,返回比target大的最小值相应的索引,如果这个最小值有多个,返回最小的索引;如果这个target比整个数组的最大元素值还要大,则不存在这个target的ceil值, 返回整个数组元素个数n。
其实这就是一个upper_bound问题,我们最后只要判断target==nums[l-1],如果成立返回l-1,否则返回l(需要保证l<len(nums))。
class Solution:
def ceil(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
l, r = 0, len(nums)-1
while l <= r:
mid = (l + r)//2
if target >= nums[mid]:
l = mid + 1
else:
r = mid - 1
if nums[l-1] == target:
return l-1
if l < len(nums):
return l
return -1
最后总结
其实二分解决的问题本质问题就是,当我们对一个问题分开考虑的时候,其中一半肯定可以去除。那么问题的核心就是左半边保留还是右半边保留?所以对于区间[l, r],有如下两种方案
第一种,左半边[l, mid]保留
while l < r:
mid = (l + r) // 2
if check(mid):
r = mid
else:
l = mid + 1
return l
第二种,右半边[mid, r]保留
while l < r:
mid = (l + r + 1) // 2
if check(mid):
l = mid
else:
r = mid - 1
return l
所以问题的关键就是这个check函数,判断我们最后要找的值是在左,还是在右。
def binarySearch1(nums, target): # 搜索左闭右闭区间
l,r = 0, len(nums) - 1
while l <= r:
mid = l + (r - l) // 2
print(nums[l:r+1])
print(l,mid, r,';',nums[l], nums[mid], nums[r])
if nums[mid] < target:
l = mid + 1
elif nums[mid] == target:
r = mid - 1
elif nums[mid] > target:
r = mid - 1
return l
def binarySearch(nums, target): # 搜索左闭右开区间
l,r = 0, len(nums)
while l < r:
mid = l + (r - l) // 2
print(nums[l:r])
print(l,mid, r,';',nums[l], nums[mid], nums[r-1])
if nums[mid] < target:
l = mid + 1
elif nums[mid] == target:
r = mid
elif nums[mid] > target:
r = mid
return l
def searchFirst(nums, x):
left, right = 0, len(nums)
while left < right: # [left, right)
mid = left + (right - left) // 2
print(left, mid, right)
if nums[mid] < x: # [mid+1, right)
left = mid + 1
elif nums[mid] == x: # [left, mid)
right = mid
elif nums[mid] > x: # [left, mid)
right = mid
if nums[left] == x:
return left
else:
return -1
def searchLast(nums, x):
left, right = 0, len(nums)
while left < right: # [left,right)
mid = left + (right - left) // 2
if nums[mid] < x: # [mid+1, right)
left = mid + 1
elif nums[mid] == x: # [mid, right)
left = mid + 1
elif nums[mid] > x: # [left, mid)
right = mid
if nums[right - 1] == x:
return right - 1
else:
return -1
nums = [3,5,6,7,7,19,43,55,55,55,55,55,55,55,55,55,55,98]
target = 7
print(nums, target)
#target = 43
#target = 45
#l = binarySearch(nums, target)
print(searchFirst(nums, target))
print(searchLast(nums, target))
浙公网安备 33010602011771号