004 二分算法:从入门到精通,一文吃透所有二分变种问题!

摘要

二分算法是解决有序数据快速查找极值优化问题的核心工具,能将时间复杂度从O(n)优化至O(logn)。无论是经典的数组搜索、旋转数组问题,还是复杂的最值优化场景(如“最大值最小化”),二分法都展现出强大的威力。本文通过LeetCode高频题目。详解二分法的核心原理与模板,帮你彻底掌握这一算法!


目录

  1. 二分法的核心思想与适用场景
  2. 基础二分查找:标准模板与变形
  3. 边界问题:寻找左右边界
  4. 旋转数组与极值问题
  5. 二分答案:最大值最小化问题
  6. 二维与复杂场景应用
  7. 二分法通用模板与避坑指南
  8. 总结与高频技巧

1. 二分法的核心思想与适用场景

1.1 核心思想

通过不断缩小搜索区间,将线性遍历的复杂度O(n)降低为O(logn)。核心步骤:

  1. 确定搜索范围的初始左右边界 leftright
  2. 计算中间位置 mid,根据比较结果调整边界
  3. 循环直到找到目标或区间闭合

1.2 适用场景

  • 有序数组查找:如升序数组找目标值(704题)
  • 极值优化问题:如“最小化最大值”(410题)
  • 部分有序数组:如旋转数组找最小值(153题)
  • 数学问题转化:如求平方根(69题)

2. 基础二分查找:标准模板与变形

例题1:二分查找

问题:在有序数组中查找目标值,返回索引,不存在则返回-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

例题2:搜索插入位置

问题:找到目标值应插入的位置(首个≥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)

3. 边界问题:寻找左右边界

例题3:在排序数组中查找元素的第一个和最后一个位置

关键点:分别用左边界和右边界模板查找起始和结束位置。

左边界模板(找第一个等于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

4. 旋转数组与极值问题

例题4:寻找旋转排序数组中的最小值

关键点:最小值一定出现在旋转点右侧,通过比较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]

例题5:搜索旋转排序数组

核心逻辑:先判断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

5. 二分答案:最大值最小化问题

例题6:分割数组的最大值

问题:将数组分割为k个子数组,使每个子数组和的最大值最小。
二分答案法步骤

  1. 确定答案范围:左边界为数组最大值,右边界为总和
  2. 验证给定最大值是否可行(贪心分割)
  3. 调整左右边界

代码实现

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

6. 二维与复杂场景应用

例题7:搜索二维矩阵II

技巧:从右上角开始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

7. 二分法通用模板与避坑指南

7.1 通用模板

# 闭区间模板(明确终止条件)
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  # 注意检查越界

7.2 避坑指南

  1. 循环条件混淆:闭区间用left <= right,左闭右开用left < right
  2. 中间值计算:使用mid = left + (right - left) // 2防止溢出
  3. 边界更新错误:左边界更新为mid + 1,右边界更新为mid - 1mid
  4. 未处理重复元素:寻找边界时需特殊处理

8. 总结与高频技巧

✅ 高频技巧

  1. 左闭右开更简洁:适合处理边界问题,减少边界条件判断
  2. 画图辅助分析:对旋转数组、二维矩阵等问题画出关键节点
  3. 二分答案法三步走:确定范围 → 验证函数 → 二分收缩
  4. 优先处理明确条件:如旋转数组中先判断mid所在区间

核心结论

掌握二分法的关键在于理解区间收缩逻辑循环不变量。通过模板变形、画图分析和大量练习,可以应对90%的二分变种问题!


互动

你在刷题时遇到过哪些“看似简单实则坑多”的二分问题?欢迎留言分享!
觉得本文有帮助?点赞⭐收藏关注,获取更多算法干货!


相关题目推荐

  1. 二分查找
  2. 搜索插入位置
  3. 在排序数组中查找元素的第一个和最后一个位置
  4. 寻找旋转排序数组中的最小值
  5. 搜索旋转排序数组
  6. 分割数组的最大值
  7. 搜索二维矩阵II

本文中所有的例题都有详细题目和解答,用 c++ 编写,直接点链接即可

你可能感兴趣的:(数据结构与算法,算法)