二分查找基本代码
# 返回 x 在 arr 中的索引,如果不存在返回 -1
def binarySearch (arr, l, r, x):
# 基本判断
if r >= l:
mid = int(l + (r - l)/2)
# 元素整好的中间位置
if arr[mid] == x:
return mid
# 元素小于中间位置的元素,只需要再比较左边的元素
elif arr[mid] > x:
return binarySearch(arr, l, mid-1, x)
# 元素大于中间位置的元素,只需要再比较右边的元素
else:
return binarySearch(arr, mid+1, r, x)
else:
# 不存在
return -1
力扣704题即为简单的二分查找题:704
力扣上用到二分查找的相关题目有:35题搜索插入位置:
力扣35题
题解代码如下链接:
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
low, high = 0, len(nums) - 1
# 在 [low, high] 找 target
while low <= high:
mid = (low + high) // 2
# 如果 target 找到,直接返回位置
if nums[mid] == target:
return mid
# 如果 target 大于中间数,则 target 可能在右区间
# 在 [mid + 1, right] 中找
elif nums[mid] < target:
low = mid + 1
# 如果 target 小于中间数,则 target 可能在左区间
# 在 [left, mid -1] 中找
else:
high = mid - 1
# 如果在数组中没找到,则返回需要插入数值的位置
return high + 1
力扣300题:最长递增子序列
这个解法我暂时没有看懂(明白了,意思就是用二分法继续接上前面的子序列,看看能不能形成新的更长子序列):
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
# 记录每种长度末尾的最小值
length_to_min = [0] * (len(nums) + 1)
length_to_min[0] = float("-inf")
length = 0 # 目前最长递增子序列长度
for i in range(len(nums)):
if nums[i] > length_to_min[length]:
# 当前的值比目前最长递增子序列的末尾最小值要大
# 所以把这个值拼在这个递增子序列后面,可以组成更长的递增子序列
# 其长度为legnth+1,末尾最小值为nums[i]
length += 1
length_to_min[length] = nums[i]
else:
# 当前的值比目前最长递增子序列的末尾最小值要小
# 不能组成更长的递增子序列
# 但是该值可能可以更新较短长度的递增子序列的末尾最小值
# 在数组length_to_min向前找到第一个比他小的值,更新该长度的后一个长度的值
# 数组length_to_min是递增的,因此可以使用二分查找,证明如下
'''
假设i < j,且length_to_min[i] >= length_to_min[j]
那长度为j的递增子序列里一定存在一个长度为i的子序列,且末尾值小于length_to_min[i]
与条件矛盾
'''
left, right = 1, length
while left <= right:
mid = (right - left) // 2 + left
if length_to_min[mid] < nums[i]:
left = mid + 1
else:
if length_to_min[mid-1] < nums[i]:
length_to_min[mid] = nums[i]
break
else:
right = mid - 1
return length
力扣34:在排序数组中查找元素的第一个和最后一个位置力扣34
自己用上面的二分法求解时候超出时间限制,相关题解为:二分法进一步的求解
class Solution(object):
def searchRange(self, nums, target):
if not nums:
return [-1,-1]
n = len(nums)
# 查找第一个和最后一个元素
def find(is_find_first):
begin = 0
end = n-1
# if和elif的逻辑跟正常的二分查找一样
while begin<=end:
mid = begin+(end-begin)//2
if nums[mid]>target:
end = mid-1
elif nums[mid]<target:
begin = mid+1
# 找到目标值了,开始定位到第一个和最后一个位置
else:
# 查找第一个和最后一个逻辑很类似,这里用一个变量标记
# 是查找第一个还是查找最后一个
if is_find_first:
# 如果不满足条件,缩小右边界,继续往左边查找
if mid>0 and nums[mid]==nums[mid-1]:
end = mid-1
else:
return mid
else:
# 如果不满足条件,增大左边界,继续往右边查找
if mid<n-1 and nums[mid]==nums[mid+1]:
begin = mid+1
else:
return mid
return -1
return [find(True), find(False)]
力扣33:搜索旋转排序数组 力扣33题
题解1:先找断点再用二分
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 先用类似二分的方法寻找分界点
left = 1
right = len(nums) - 1
while (left <= right):
mid = (left + right) // 2
if nums[mid] < nums[0]:
right = mid - 1
else:
left = mid + 1
diving_line =left
if nums[0] == target:
return 0
elif nums[0] > target: # 这种情况target应该处于另一个区域
if self.binary_search(nums[diving_line:], target) != -1:
return self.binary_search(nums[diving_line:], target) + diving_line
else:
return -1
else:
return self.binary_search(nums[0:diving_line], target)
#标准的二分查找找某个特定的值
def binary_search(self, list, target):
left = 0
right = len(list) - 1
while (left <= right):
mid = (left + right) // 2
if list[mid] == target:
return mid
elif list[mid] > target:
right = mid - 1
else:
left = mid + 1
return -1
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums:
return -1
L, R = 0, len(nums) - 1
while L <= R:
M = L + (R - L) // 2
if nums[M] == target:
return M
elif nums[L] <= nums[M]: # L ~ M 有序
if nums[L] <= target < nums[M]: # target 存在于 有序数组中,二分
R = M - 1
else: # target 不存在于 L ~ M中,到右边去找
L = M + 1
else: # M ~ R有序
if nums[M] < target <= nums[R]: # target 存在于 有序数组中,二分
L = M + 1
else: # target 不存在于 M ~ R中,到左边去找
R = M - 1
return -1
二分查找最强总结:内功心法加模版
力扣81题搜索旋转数组
用力扣33题的两种解法均不能解决,相关题解如下:
题解33题
最值得关注的点在于continue命令,直接避免了当nums[left]=nums[right]时候不知道target在mid左边还是右边的情况
class Solution(object):
def search(self, nums, target):
if not nums: return -1
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) / 2
if nums[mid] == target:
return True
if nums[left] == nums[right]:
left += 1
continue
if nums[mid] <= nums[right]:
if target > nums[mid] and target <= nums[right]:
left = mid + 1
else:
right = mid - 1
else:
if target < nums[mid] and target >= nums[left]:
right = mid - 1
else:
left = mid + 1
return False