基础算法精讲·题目汇总:灵茶山艾府 - 【基础算法精讲】- GitHub
视频:灵茶山艾府的个人空间-灵茶山艾府个人主页-哔哩哔哩视频
二分查找
【题单】二分:https://leetcode.cn/circle/discuss/SqopEo/
04 二分查找 红蓝染色法
课程讲解
原始二分查找(模板代码)
要求 nums 是非递减的,即 nums[i] <= nums[i+1],返回最小的满足 nums[i] >= target 的 i。如果不存在,返回 len(nums)
二分查找在闭区间上的写法,以及对比开区间、半闭半开区间上的写法
≥、>target(的第一个数)、≤、<target(的最后一个数)写法的差别
- >x 等价于 ≥ x+1(的第一个数)
- <x 可以看成 ≥ x 的第一个数,它左边的那个数
- ≤x 可以看成 > x 的第一个数,它左边的那个数



红色更新 left 指针,蓝色更新 right 指针
python
# 左闭右闭
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
# [left, right]
while left <= right: # 区间不为空
mid = (left + right) // 2 # 或写成 left + (right - left) // 2
if nums[mid] < target:
left = mid + 1 # [mid+1, right]
else:
right = mid - 1 # [left, mid-1]
return left
# 左闭右开
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)
# [left, right)
while left < right: # 区间不为空
mid = (left + right) // 2 # 或写成 left + (right - left) // 2
if nums[mid] < target:
left = mid + 1 # [mid+1, right)
else:
right = mid # [left, mid)
return left # 或 return right 均可
# 左开右开
def search(self, nums: List[int], target: int) -> int:
left = -1
right = len(nums)
# (left, right)
while left+1 < right: # 区间不为空
mid = (left + right) // 2 # 或写成 left + (right - left) // 2
if nums[mid] < target:
left = mid # (mid, right)
else:
right = mid # (left, mid)
return right
- 由于每次都去掉了一半的元素,时间复杂度为 O(logn)
- 空间复杂度 O(1),没有用到额外空间
34. 在排序数组中查找元素的第一个和最后一个位置
等于求 target 的开始位置和结束位置,即分别是 ≥ 和 ≤
python
class Solution:
# 左闭右闭版本的模板代码
def search(self, nums, target):
left = 0
right = len(nums) - 1
# [left, right]
while left <= right: # 区间不为空
mid = (left + right) // 2 # 或写成 left + (right - left) // 2
if nums[mid] < target:
left = mid + 1 # [mid+1, right]
else:
right = mid - 1 # [left, mid-1]
return left
def searchRange(self, nums: List[int], target: int) -> List[int]:
start = self.search(nums, target) # ≥ target的第一个位置
# 如果所有数都 < target 或 这个数不等于target
if start == len(nums) or nums[start] != target:
return [-1, -1]
# ≤ target的最后一个位置,可以转化成
# > target的第一个数,它左边的那个数
# > target等价于 ≥ target + 1
end = self.search(nums, target+1) - 1 # -1表示它左边的那个数
return [start, end]
- 时间O(logn),空间O(1)
课后作业
05 数组峰值 搜索旋转排序数组
课程讲解
162. 寻找峰值
找到一个峰顶,大于左右两侧相邻的元素
- 比如下图中的2(第一个2)、4、6(因为可以假设
nums[-1] = nums[n] = -∞)都是峰顶 - 由于峰顶一定在数组中,所以数组最右侧的元素一定是蓝色的(n-1要么是峰顶,要么在峰顶右侧)
- 因此二分时,可以初始化 left=0,right=n-2(n-1一定是蓝色,无需再二分)
- 可以通过比较 M 和 M+1 指向的数字来染色。题目保证了这两个数字一定不相等(对于所有有效的
i都有nums[i] != nums[i + 1]),所以要么小于,要么大于 - 若是小于,说明 M 在峰顶左侧(M右侧存在峰顶),都是红色,更新left
- 若是大于,说明 M 要么是峰顶,要么在峰顶右侧(M左侧存在峰顶),都是蓝色,更新right
- 二分循环结束后,L就是答案(左闭右闭写法时)


python
# 左闭右闭写法
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
# [0, n-2]
left, right = 0, len(nums)-2
while left <= right:
mid = (left + right) // 2
if nums[mid] < nums[mid+1]:
left = mid + 1
else:
right = mid - 1
return left
# 左开右开写法
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
# [0, n-2]
# (-1, n-1)
left, right = -1, len(nums)-1
while left + 1 < right:
mid = (left + right) // 2
if nums[mid] < nums[mid+1]: # 红色
left = mid
else: # 蓝色
right = mid
return right
- 时间O(logn),空间O(1)
153. 寻找旋转排序数组中的最小值
给你一个数组,它可能是一个递增的数组,也有可能是两段递增数组且第一个数 > 最后一个数 。如何用 O(logn) 的时间 找到数组的最小值?
- 需要一个判定方式来判断 nums[mid](即二分的位置)是在最小值的左侧还是右侧
- 可以和最后一个数比大小。由于最小值一定在数组中,那么最后一个数要么是最小值,要么在最小值的右侧。因此 n-1 一定是蓝色
- 因此,在 0 ~ n-2 中二分
- 如果 nums[mid] < 最后一个数,那么 nums[mid] 所处的位置有两种情况:在一段递增数组中,或者在两段递增数组中的第二段。无论是哪种情况,nums[mid] 要么是最小值,要么在最小值右侧。染成蓝色
- 如果 nums[mid] > 最后一个数,那么 nums[mid] 只可能在两段递增数组中,且一定在最小值左侧(第一段)。染成红色

python
class Solution:
def findMin(self, nums: List[int]) -> int:
# [0, n-2]
# (-1, n-1)
left, right = -1, len(nums)-1
while left + 1 < right:
mid = (left + right) // 2
if nums[mid] > nums[-1]: # 红色
left = mid
else: # 蓝色
right = mid
return nums[right]
33. 搜索旋转排序数组
【力扣-Python-33】搜索旋转排序数组(middle)
找 target(可能不在数组中),需要在 [0, n-1] 上二分,有两种做法:
- 参考153题,首先找到最小值,然后比较 target 和最后一个数的大小,来判断在哪段二分查找 target。需要两次二分
- 可以只一次二分。分三种情况讨论,什么时候 nums[mid] 在 target 及其右侧(染成蓝色)
- 如果二分的位置 > 最后一个数,说明在第一段。如果此时 target 也大于最后一个数,说明 target 也在第一段。且如果 nums[mid] >= target,说明在 target 及其右侧,染成蓝色
- 如果二分的位置 ≤ 最后一个数,说明在第二段。如果此时 target 大于最后一个数,说明 target 在第一段。直接就说明 nums[mid] 在 target 及其右侧(染成蓝色)
- 如果二分的位置 ≤ 最后一个数,说明在第二段。target 也在第二段,nums[mid] >= target,这种情况也是蓝色
- 其余情况就是红色
python
class Solution:
def is_blue(self, nums, i, target):
end = nums[-1]
if nums[i] > end:
return target > end and nums[i] >= target
else:
return target > end or nums[i] >= target
# [0, n-1]
# (-1, n)
def search(self, nums: List[int], target: int) -> int:
left, right = -1, len(nums)
while left + 1 < right:
mid = (left + right) // 2
if self.is_blue(nums, mid, target):
right = mid
else:
left = mid
if right == len(nums) or nums[right] != target:
return -1
return right