二分查找——算法总结与教学指南

📚 算法核心思想

二分查找的本质

  • 有序 集合中通过不断折半缩小搜索范围
  • 每次比较都能排除一半的错误答案
  • 核心前提:数据必须有序(直接或间接)

三种二分查找模式

模式 特点 适用场景 关键判断
标准二分 查找确切存在的值 有序数组查找 nums[mid] == target
寻找边界 查找第一个/最后一个位置 重复元素、插入位置 条件变化时的边界处理
抽象二分 在抽象条件上二分 答案有单调性、最优化问题 定义判定函数

🔧 通用解题框架

基础二分查找模板

cpp 复制代码
int binarySearch(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;  // 防止溢出
        
        if (nums[mid] == target) {
            return mid;                       // 找到目标
        } else if (nums[mid] < target) {
            left = mid + 1;                   // 搜索右半部分
        } else {
            right = mid - 1;                  // 搜索左半部分
        }
    }
    
    return -1;  // 未找到
}

寻找边界的二分模板

cpp 复制代码
// 寻找第一个 >= target 的位置(lower_bound)
int findFirstGreaterOrEqual(vector<int>& nums, int target) {
    int left = 0, right = nums.size();  // 注意:右开区间
    
    while (left < right) {
        int mid = left + (right - left) / 2;
        
        if (nums[mid] >= target) {
            right = mid;                // 保留mid,继续向左找
        } else {
            left = mid + 1;             // 向右找
        }
    }
    
    return left;  // 第一个 >= target 的位置
}

🎯 问题识别技巧

什么时候用二分查找?

  1. 明显关键词:有序、排序、搜索、查找
  2. 数据规模大:O(n) 会超时,需要 O(log n)
  3. 答案单调性:条件满足性随参数单调变化

判断依据

cpp 复制代码
// 如果是这些问题,考虑二分查找:
- "在排序数组中查找元素的第一个和最后一个位置"
- "寻找旋转排序数组中的最小值"
- "在 D 天内送达包裹的能力"(最优化问题)
- "制作 m 束花所需的最少天数"
- "分割数组的最大值"

🔍 关键决策点

1. 如何选择区间表示?

  • 左闭右闭 [left, right]while(left <= right),更新时 mid±1
  • 左闭右开 [left, right)while(left < right),更新时 left=mid+1right=mid

2. 如何判断搜索方向?

cpp 复制代码
// 不同问题的判断条件
if (nums[mid] == target)        // 标准查找
if (nums[mid] >= target)        // 寻找左边界
if (nums[mid] > target)         // 寻找右边界
if (canFinish(piles, mid, h))   // 最优化问题(抽象条件)

3. 如何避免死循环?

  • 确保每次循环区间都在缩小
  • 注意 mid 的计算方式:mid = left + (right-left)/2(向下取整)
  • 更新边界时至少移动一步:left = mid+1right = mid-1

💡 经典问题分类与解法

类型1:精确查找

cpp 复制代码
// 问题:在排序数组中查找目标值
int search(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        
        if (nums[mid] == target) return mid;
        else if (nums[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    
    return -1;
}

类型2:寻找边界

cpp 复制代码
// 问题:在排序数组中查找元素的第一个和最后一个位置
vector<int> searchRange(vector<int>& nums, int target) {
    // 找左边界:第一个 >= target 的位置
    int left = lower_bound(nums.begin(), nums.end(), target) - nums.begin();
    
    // 找右边界:第一个 > target 的位置 - 1
    int right = upper_bound(nums.begin(), nums.end(), target) - nums.begin() - 1;
    
    if (left <= right) return {left, right};
    return {-1, -1};
}

类型3:旋转数组查找

cpp 复制代码
// 问题:搜索旋转排序数组
int searchRotated(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        
        if (nums[mid] == target) return mid;
        
        // 判断哪一部分是有序的
        if (nums[left] <= nums[mid]) {  // 左半部分有序
            if (nums[left] <= target && target < nums[mid]) {
                right = mid - 1;  // 目标在有序的左半部分
            } else {
                left = mid + 1;   // 目标在右半部分
            }
        } else {  // 右半部分有序
            if (nums[mid] < target && target <= nums[right]) {
                left = mid + 1;   // 目标在有序的右半部分
            } else {
                right = mid - 1;  // 目标在左半部分
            }
        }
    }
    
    return -1;
}

类型4:最优化问题(抽象二分)

cpp 复制代码
// 问题:在 D 天内送达包裹的能力
int shipWithinDays(vector<int>& weights, int days) {
    // 确定二分搜索边界
    int left = *max_element(weights.begin(), weights.end());  // 最小能力
    int right = accumulate(weights.begin(), weights.end(), 0); // 最大能力
    
    while (left < right) {
        int mid = left + (right - left) / 2;
        
        if (canShip(weights, days, mid)) {
            right = mid;  // 尝试更小的能力
        } else {
            left = mid + 1;  // 需要更大的能力
        }
    }
    
    return left;
}

bool canShip(vector<int>& weights, int days, int capacity) {
    int current = 0, needed = 1;
    
    for (int weight : weights) {
        if (current + weight > capacity) {
            needed++;
            current = 0;
        }
        current += weight;
    }
    
    return needed <= days;
}

🚀 教学要点

给初学者的建议

  1. 从理解原理开始

    复制代码
    二分查找为什么是 O(log n)?
    每次比较排除一半元素 → 最多 log₂n 次
  2. 掌握两种模板

    • 模板1:精确查找(闭区间)
    • 模板2:边界查找(左闭右开)
  3. 手动模拟过程

    cpp 复制代码
    数组:[1, 3, 5, 7, 9, 11]
    查找:7
    
    步骤1:left=0, right=5, mid=2, nums[2]=5 < 7 → left=3
    步骤2:left=3, right=5, mid=4, nums[4]=9 > 7 → right=3
    步骤3:left=3, right=3, mid=3, nums[3]=7 == 7 → 找到
  4. 注意常见陷阱

    • 整数溢出:使用 mid = left + (right-left)/2
    • 死循环:确保每次循环边界都在变化
    • 边界条件:空数组、单个元素、目标不存在

二分查找的思维转换

  1. 从"找答案"到"猜答案+验证"

    复制代码
    传统:直接找到正确答案
    二分:猜一个答案 → 验证是否正确 → 调整猜测
  2. 判定函数的编写

    cpp 复制代码
    // 最优化问题的关键:编写判定函数
    bool isValid(int guess) {
        // 判断 guess 是否满足条件
        // 通常需要 O(n) 或 O(m) 的验证
    }

练习题进阶路径

复制代码
Level 1: 基础二分查找(704题)
Level 2: 寻找边界(34题)
Level 3: 旋转数组搜索(33、81题)
Level 4: 抽象二分查找(875、1011题)
Level 5: 二维二分查找(74、240题)
Level 6: 复杂最优化问题(410、1231题)

📝 一句话总结各类问题

  1. 标准二分:有序数组中找目标值
  2. 寻找边界lower_boundupper_bound 的运用
  3. 旋转数组:先判断哪边有序,再决定搜索方向
  4. 峰值查找:比较 mid 和 mid+1,向更高的方向搜索
  5. 最优化问题:二分搜索答案,编写判定函数验证
  6. 二维二分:将二维映射到一维,或行列分别二分

🎁 终极心法

二分查找 = 有序数据 + 折半排除 + 边界处理

四步解题法

  1. 判断是否能用二分:数据是否有序?答案是否有单调性?
  2. 确定搜索范围:最小可能值和最大可能值是什么?
  3. 编写判定函数:如何验证一个猜测值是否可行?
  4. 处理边界情况:空数组、找不到、重复元素等

调试技巧

cpp 复制代码
// 在循环中添加打印,观察搜索过程
while (left <= right) {
    int mid = left + (right - left) / 2;
    cout << "left=" << left << ", right=" << right 
         << ", mid=" << mid << ", nums[mid]=" << nums[mid] << endl;
    // ...
}

掌握二分查找的关键在于理解其"减而治之"的思想,并通过大量练习熟悉各种变体。从标准二分开始,逐步挑战更复杂的问题,最终能够灵活运用二分思想解决各类最优化问题。

相关推荐
IronMurphy1 天前
【算法二十七】230. 二叉搜索树中第 K 小的元素 199. 二叉树的右视图
算法·深度优先
暮冬-  Gentle°1 天前
C++中的工厂方法模式
开发语言·c++·算法
沐硕1 天前
《基于改进协同过滤与多目标优化的健康饮食推荐系统设计与实现》
java·python·算法·fastapi·多目标优化·饮食推荐·改进协同过滤
Z9fish1 天前
sse哈工大C语言编程练习47
c语言·数据结构·算法
nglff1 天前
蓝桥杯抱佛脚第一天|简单模拟,set,map的使用
算法·职场和发展·蓝桥杯
愣头不青1 天前
560.和为k的子数组
java·数据结构
仟濹1 天前
【算法打卡day27(2026-03-19 周四)】蓝桥云课中Lv.1难度中的绝大部分题
算法·蓝桥杯
罗湖老棍子1 天前
滑动窗口与双调队列:幕布覆盖问题(定右缩左满分板子)改编自LeetCode 1438
算法·滑动窗口·单调队列
CoovallyAIHub1 天前
ICLR 2026 | MedAgent-Pro:用 Agent 工作流模拟临床医生的循证诊断过程
深度学习·算法·计算机视觉
实心儿儿1 天前
算法7:两个数组的交集
算法·leetcode·职场和发展