跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

二分查找算法详解与实战模板

二分查找是有序数组中的核心搜索算法,系统讲解其基本原理、边界处理及常见变体。涵盖朴素二分查找、查找左右边界的模板写法,重点分析循环终止条件与 mid 计算防溢出技巧。结合搜索插入位置、x 的平方根、山脉数组峰顶、旋转排序数组最小值及缺失数字等经典题目,提供 C++ 代码实现与详细解析,帮助读者掌握 O(log n) 时间复杂度的解题思路。

steve发布于 2026/3/21更新于 2026/5/2320 浏览
二分查找算法详解与实战模板

二分查找算法详解与实战模板

二分查找是有序数组中的核心搜索算法,其本质是通过不断折半缩小搜索范围,直到找到目标元素。虽然最朴素的使用场景是在升序数组中查找指定值,但通过调整边界策略,它可以解决更复杂的问题,如查找区间、峰值元素等。

1. 基础二分查找

我们先从经典的 LeetCode 704 题入手,巩固最朴素的二分查找逻辑。

题目描述

给定一个升序数组 nums 和一个目标值 target,找出 target 在数组中的下标。如果不存在则返回 -1。

实现思路

初始化两个指针 left 和 right,分别指向数组首尾。计算中间位置 mid,比较 nums[mid] 与 target:

  • 若 nums[mid] > target,说明目标在左半部分,将 right 移至 mid - 1。
  • 若 nums[mid] < target,说明目标在右半部分,将 left 移至 mid + 1。
  • 若相等,直接返回 mid。

循环终止条件为 left <= right。当 left > right 时仍未找到,说明目标不存在。

注意: 计算 mid 时,直接使用 (left + right) / 2 在数组极长时可能导致整数溢出。建议使用 left + (right - left) / 2 或 left + (right - left + 1) / 2 来避免此问题。

class Solution {
public:
    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) {
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
};

2. 查找左右边界的二分查找

当需要查找目标值的第一个或最后一个位置(例如 LeetCode 34 题)时,朴素二分查找可能无法直接满足要求,或者效率不够高。此时需要明确区分查找左边界和右边界的模板。

核心差异

  1. 循环条件:通常使用 left < right。当 left == right 时,指针已收敛到唯一候选位置,无需继续判断。
  2. Mid 计算:
    • 查找左边界时,若 mid 偏大导致死循环,需向下取整:mid = left + (right - left) / 2。
    • 查找右边界时,若 mid 偏小导致死循环,需向上取整:mid = left + (right - left + 1) / 2。
  3. 更新策略:
    • 找左边界:若 nums[mid] >= target,则 right = mid;否则 left = mid + 1。
    • 找右边界:若 nums[mid] <= target,则 left = mid;否则 right = mid - 1。

代码实现

以下代码同时实现了查找左端点和右端点的逻辑,并处理了目标值不存在的情况。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.empty()) return {-1, -1};

        // 查找左端点
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        int begin = left;

        // 查找右端点
        left = 0; right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left + 1) / 2;
            if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid;
            }
        }
        int end = left;

        // 验证是否真的存在 target
        if (begin < nums.size() && nums[begin] == target) {
            return {begin, end};
        }
        return {-1, -1};
    }
};

经验总结:不要死记硬背模板。关键在于分析题意,确定搜索区间的二段性。记住一点:当 if...else 分支中出现 mid - 1 时,mid 的计算通常需要向上取整以防死循环。

3. 实战演练

通过几道经典题目进一步巩固二分查找的应用。

3.1 搜索插入位置 (LeetCode 35)

需求:在有序数组中找到目标值下标,若不存在则返回应插入的位置以保持有序。 思路:本质上仍是查找左边界。若最终 target 大于数组末尾元素,则插入位置为 left + 1。

class Solution {
public:
    int searchInsert(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) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        if (target > nums[left]) return left + 1;
        return left;
    }
};

3.2 x 的平方根 (LeetCode 69)

需求:计算非负整数 x 的算术平方根的整数部分。 思路:寻找最大的 k 使得 k * k <= x。这属于查找右边界问题。注意 mid * mid 可能溢出,需使用 long long。

class Solution {
public:
    int mySqrt(int x) {
        if (x == 0) return 0;
        int left = 1, right = x;
        while (left < right) {
            long long mid = left + (right - left + 1) / 2;
            if (mid * mid > x) {
                right = mid - 1;
            } else {
                left = mid;
            }
        }
        return left;
    }
};

3.3 山脉数组的峰顶索引 (LeetCode 852)

需求:在山脉数组中找到峰值元素的索引。 思路:数组先增后减,具有二段性。若 arr[mid] < arr[mid + 1],说明处于上升坡,峰值在右侧;反之在左侧。

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int left = 0, right = arr.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] < arr[mid + 1]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
};

3.4 旋转排序数组中的最小值 (LeetCode 153)

需求:在旋转后的升序数组中找到最小值。 思路:利用数组后半段小于等于前半段的特性。若 nums[mid] > nums[n - 1],说明最小值在右侧;否则在左侧(含 mid)。

class Solution {
public:
    int findMin(vector<int>& nums) {
        int n = nums.size();
        if (nums[0] < nums[n - 1]) return nums[0];
        int left = 0, right = n - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[n - 1]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return nums[left];
    }
};

3.5 0〜n-1 中缺失的数字

需求:在长度为 n 的递增数组中找出缺失的一个数字。 思路:若 nums[i] == i,说明缺失数字在右侧;否则在左侧。这是典型的查找左边界变体。

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int left = 0, right = records.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (records[mid] == mid) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        if (records[left] == left) left++;
        return left;
    }
};

掌握二分查找的关键在于识别问题的二段性,并根据具体需求选择合适的边界收缩策略。多练习不同变体,能显著提升对算法边界的敏感度。

目录

  1. 二分查找算法详解与实战模板
  2. 1. 基础二分查找
  3. 题目描述
  4. 实现思路
  5. 2. 查找左右边界的二分查找
  6. 核心差异
  7. 代码实现
  8. 3. 实战演练
  9. 3.1 搜索插入位置 (LeetCode 35)
  10. 3.2 x 的平方根 (LeetCode 69)
  11. 3.3 山脉数组的峰顶索引 (LeetCode 852)
  12. 3.4 旋转排序数组中的最小值 (LeetCode 153)
  13. 3.5 0〜n-1 中缺失的数字
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 应届生春招避坑指南:识别三无公司与保障劳动权益
  • Windows 系统安装 Neo4j 图数据库指南
  • 01 - 大模型推理框架选型入门:Ollama、llama.cpp与vLLM全景对比
  • 基于 DeepSeek 与 Ollama 的代码审计工具开发实录
  • SpringBoot 配置文件核心用法(Properties & YAML)
  • 在鸿蒙游戏开发中接入 AI NPC 的体验与实践
  • 构建离线私有 GPT:实现本地大模型与文档问答
  • gpt-oss-20b WEBUI 功能测评及 Ollama 无缝集成
  • Proxmox VE (PVE) 下载和安装 Kali Linux 教程
  • RTD1296PB 与 RK3568:NAS 与智能家居芯片选型实战对比
  • ROS2机器人slam_toolbox建图零基础
  • 云端多模型并行部署与写作能力对比测试方案
  • Silly Tavern 角色卡与世界书导入教程
  • GitHub Copilot Pro 学生免费订阅认证与 VS Code 集成指南
  • GitHub 国内镜像站汇总与高速访问方案
  • DeerFlow 基础教程:控制台 UI 与 Web UI 双模式使用详解
  • C++ 红黑树原理与实现详解
  • 远程控制安全与软件测评:ToDesk、AnyDesk、向日葵对比
  • IntelliJ IDEA 运行时报错 ExceptionInInitializerError 解决方案
  • 使用 Biopython 快速解析 FASTA 与 GenBank 基因数据

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online