二分算法是解决有序数据快速查找和极值优化问题的核心工具,能将时间复杂度从O(n)优化至O(logn)。无论是经典的数组搜索、旋转数组问题,还是复杂的最值优化场景(如“最大值最小化”),二分法都展现出强大的威力。本文通过LeetCode高频题目。详解二分法的核心原理与模板,帮你彻底掌握这一算法!
通过不断缩小搜索区间,将线性遍历的复杂度O(n)降低为O(logn)。核心步骤:
left
和 right
mid
,根据比较结果调整边界问题:在有序数组中查找目标值,返回索引,不存在则返回-1。
标准模板:
def binary_search(nums, target):
left, right = 0, len(nums) - 1
while left <= right: # 闭区间
mid = left + (right - left) // 2 # 防溢出
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
问题:找到目标值应插入的位置(首个≥target的索引)。
左边界模板:
def search_insert(nums, target):
left, right = 0, len(nums) # 右边界开区间
while left < right:
mid = left + (right - left) // 2
if nums[mid] >= target:
right = mid
else:
left = mid + 1
return left # 若target比所有元素大,left会等于len(nums)
关键点:分别用左边界和右边界模板查找起始和结束位置。
左边界模板(找第一个等于target的位置):
def find_left(nums, target):
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] >= target:
right = mid
else:
left = mid + 1
return left if left < len(nums) and nums[left] == target else -1
右边界模板(找最后一个等于target的位置):
def find_right(nums, target):
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] > target:
right = mid
else:
left = mid + 1
return left - 1 if left > 0 and nums[left-1] == target else -1
关键点:最小值一定出现在旋转点右侧,通过比较nums[mid]与nums[right]调整区间。
def find_min(nums):
left, right = 0, len(nums) - 1
while left < right:
mid = left + (right - left) // 2
if nums[mid] > nums[right]: # 最小值在右侧
left = mid + 1
else: # 最小值在左侧或mid处
right = mid
return nums[left]
核心逻辑:先判断mid在左半段还是右半段,再判断target所在区间。
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 判断mid在左半段还是右半段
if nums[mid] >= nums[left]: # 左半段有序
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else: # 右半段有序
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
问题:将数组分割为k个子数组,使每个子数组和的最大值最小。
二分答案法步骤:
代码实现:
def split_array(nums, k):
left, right = max(nums), sum(nums)
def is_valid(max_sum):
count, curr = 1, 0
for num in nums:
if curr + num > max_sum:
count += 1
curr = num
else:
curr += num
if count > k:
return False
return True
while left < right:
mid = left + (right - left) // 2
if is_valid(mid):
right = mid
else:
left = mid + 1
return left
技巧:从右上角开始Z字形搜索,逐步缩小范围。
def search_matrix(matrix, target):
if not matrix:
return False
row, col = 0, len(matrix[0]) - 1
while row < len(matrix) and col >= 0:
if matrix[row][col] == target:
return True
elif matrix[row][col] > target:
col -= 1
else:
row += 1
return False
# 闭区间模板(明确终止条件)
def binary_search_template(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# 左闭右开模板(适合找边界)
def left_bound_template(nums, target):
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] >= target:
right = mid
else:
left = mid + 1
return left # 注意检查越界
left <= right
,左闭右开用left < right
mid = left + (right - left) // 2
防止溢出mid + 1
,右边界更新为mid - 1
或mid
掌握二分法的关键在于理解区间收缩逻辑和循环不变量。通过模板变形、画图分析和大量练习,可以应对90%的二分变种问题!
你在刷题时遇到过哪些“看似简单实则坑多”的二分问题?欢迎留言分享!
觉得本文有帮助?点赞⭐收藏关注,获取更多算法干货!
相关题目推荐:
本文中所有的例题都有详细题目和解答,用 c++ 编写,直接点链接即可