跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

二分查找算法详解与经典题解

综述由AI生成深入解析二分查找的核心思想,涵盖基础查找、边界定位、插入位置及变体问题。通过 C++ 代码示例,演示了如何避免溢出、死循环等常见陷阱,并结合山脉数组、旋转排序数组等场景,提供实用的解题模板与思路总结。重点讲解了左右边界的收缩策略及 mid 取整对循环终止的影响。

松间照月发布于 2026/3/21更新于 2026/5/912 浏览

二分查找核心逻辑与实战

二分查找是算法面试中的高频考点,其核心在于利用数据的有序性,将查找范围不断减半。掌握二分查找不仅要求会写代码,更要理解边界处理、溢出风险以及不同变体下的区间收缩策略。

1. 基础二分查找

思路解析: 定义左右指针 left 和 right,分别指向数组的起始和结束位置。每次计算中间点 mid,根据 nums[mid] 与目标值 target 的大小关系决定搜索方向:

  • 若相等,直接返回索引;
  • 若 nums[mid] > target,说明目标在左半部分,更新 right = mid - 1;
  • 若 nums[mid] < target,说明目标在右半部分,更新 left = mid + 1。 当 left > right 时仍未找到,返回 -1。

注意: 计算 mid 时建议使用 left + (right - left) / 2 而非 (left + right) / 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) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
};

2. 查找元素的第一个和最后一个位置

这道题需要找到目标值在排序数组中的起始和结束下标。核心依然是二分,但需要根据需求调整区间的收缩方式。

寻找左边界: 我们要找的是第一个等于 target 的位置。此时区间特点为:左侧小于 target,右侧大于等于 target。

  • 若 nums[mid] >= target,说明 mid 可能是结果或结果在左边,保留 mid,令 right = mid。
  • 若 nums[mid] < target,说明结果在右边,令 left = mid + 1。 这里 mid 需向下取整,防止 left = mid + 1 导致死循环。
  • 寻找右边界: 我们要找的是最后一个等于 target 的位置。区间特点为:左侧小于等于 target,右侧大于 target。

    • 若 nums[mid] <= target,说明 mid 可能是结果或结果在右边,保留 mid,令 left = mid。
    • 若 nums[mid] > target,说明结果在左边,令 right = mid - 1。 这里 mid 需向上取整(即 +1),防止 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;
            }
            if (nums[left] != target) return {-1, -1};
            int begin = left;
            
            // 找右边界
            left = 0; right = nums.size() - 1;
            while (left < right) {
                int mid = left + (right - left + 1) / 2;
                if (nums[mid] <= target) left = mid;
                else right = mid - 1;
            }
            return {begin, right};
        }
    };
    

    3. 搜索插入位置

    如果目标不存在,需要返回它应该插入的位置。这其实是一个寻找'第一个大于等于 target 的元素'的问题。

    思路:

    • 若 nums[mid] >= target,说明插入点在 mid 或其左侧,令 right = mid。
    • 若 nums[mid] < target,说明插入点在 mid 右侧,令 left = mid + 1。 循环结束时 left == right,即为插入位置。
    class Solution {
    public:
        int searchInsert(vector<int>& nums, int target) {
            int left = 0, right = nums.size();
            while (left < right) {
                int mid = left + (right - left) / 2;
                if (nums[mid] < target) left = mid + 1;
                else right = mid;
            }
            return left;
        }
    };
    

    4. x 的平方根

    求一个非负整数 x 的平方根的整数部分。虽然可以用暴力枚举,但二分法效率更高。

    思路:

    • 区间 [0, x] 具有二段性:小于等于 sqrt(x) 的数平方后小于等于 x,大于的则大于 x。
    • 注意乘法可能溢出,使用 long long 存储中间结果。
    • 若 mid * mid <= x,说明 mid 可能是答案或答案在右边,令 left = mid(需向上取整)。
    • 否则令 right = mid - 1。
    class Solution {
    public:
        int mySqrt(int x) {
            if (x < 1) return 0;
            int left = 1, right = x;
            while (left < right) {
                long long mid = left + (right - left + 1) / 2;
                if (mid * mid <= x) left = mid;
                else right = mid - 1;
            }
            return left;
        }
    };
    

    5. 山脉数组的峰顶索引

    山脉数组先严格递增再严格递减。我们需要找到峰值的索引。

    思路:

    • 比较 arr[mid] 与 arr[mid-1]。
    • 若 arr[mid] > arr[mid-1],说明处于上升阶段,峰值在右侧,令 left = mid。
    • 若 arr[mid] < arr[mid-1],说明处于下降阶段,峰值在左侧,令 right = mid - 1。
    • 同样需要注意 mid 的取整方式以避免死循环。
    class Solution {
    public:
        int peakIndexInMountainArray(vector<int>& arr) {
            int left = 1, right = arr.size() - 2;
            while (left < right) {
                int mid = left + (right - left + 1) / 2;
                if (arr[mid] > arr[mid - 1]) left = mid;
                else right = mid - 1;
            }
            return left;
        }
    };
    

    6. 寻找峰值

    峰值元素是指其值严格大于左右相邻值的元素。数组两端可视为负无穷。

    思路:

    • 只要存在一段单调递增或递减的区域,就能确定峰值在哪一侧。
    • 比较 nums[mid] 和 nums[mid+1]。
    • 若 nums[mid] < nums[mid+1],说明右侧有上升趋势,峰值必在右侧,令 left = mid + 1。
    • 否则,峰值在左侧(包括 mid),令 right = mid。
    class Solution {
    public:
        int findPeakElement(vector<int>& nums) {
            int left = 0, right = nums.size() - 1;
            while (left < right) {
                int mid = left + (right - left) / 2;
                if (nums[mid] < nums[mid + 1]) left = mid + 1;
                else right = mid;
            }
            return left;
        }
    };
    

    7. 寻找旋转排序数组中的最小值

    数组原本是升序,但在某个点进行了旋转。例如 [3,4,5,1,2]。

    思路:

    • 利用旋转特性,数组分为两段:前段大,后段小。
    • 比较 mid 与右端点 nums[right]。
    • 若 nums[mid] > nums[right],说明 mid 在前段,最小值在右侧,令 left = mid + 1。
    • 若 nums[mid] <= nums[right],说明 mid 在后段,最小值在左侧(含 mid),令 right = mid。
    class Solution {
    public:
        int findMin(vector<int>& nums) {
            int left = 0, right = nums.size() - 1;
            while (left < right) {
                int mid = left + (right - left) / 2;
                if (nums[mid] > nums[right]) left = mid + 1;
                else right = mid;
            }
            return nums[left];
        }
    };
    

    8. 点名(缺失的数字)

    在一个升序数组中,有一个数字缺失。数组下标从 0 开始,正常情况下 nums[i] == i。

    思路:

    • 缺失位置之前,nums[i] == i;缺失位置之后,nums[i] > i。
    • 这是一个典型的二段性问题。
    • 若 nums[mid] == mid,说明缺失在右侧,令 left = mid + 1。
    • 否则,缺失在左侧(含 mid),令 right = mid。
    • 最后检查 left 是否越界或对应值是否匹配,处理边界情况。
    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 (mid == records[mid]) left = mid + 1;
                else right = mid;
            }
            if (left == records[left]) return left + 1;
            return left;
        }
    };
    

    目录

    1. 二分查找核心逻辑与实战
    2. 1. 基础二分查找
    3. 2. 查找元素的第一个和最后一个位置
    4. 3. 搜索插入位置
    5. 4. x 的平方根
    6. 5. 山脉数组的峰顶索引
    7. 6. 寻找峰值
    8. 7. 寻找旋转排序数组中的最小值
    9. 8. 点名(缺失的数字)
    • 💰 8折买阿里云服务器限时8折了解详情
    • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
    • 代充Chatgpt Plus/pro 帐号了解详情
    • 🤖 一键搭建Deepseek满血版了解详情
    • 一键打造专属AI 智能体了解详情
    极客日志微信公众号二维码

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

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

    更多推荐文章

    查看全部
    • C++ 类与对象进阶:构造函数、拷贝构造与操作符重载
    • AVL 树原理与 C++ 实现
    • OpenClaw:从认知到执行的行动型 AI 框架解析
    • VS Code + Conda 零基础搭建 Python 数据可视化环境
    • SubtitleEdit Purfview Faster Whisper XXL 引擎安装失败排查与修复
    • C++ 内核性能优化十大误区:如何避免常见陷阱
    • C++ 模板编程基础:泛型编程入门与实践
    • Ubuntu 22.04 下 libwebkit2gtk-4.1-0 安装与避坑指南
    • Visual C++ 运行库诊断与部署完整方案
    • C++ 常用数学函数:__gcd 与 pow 用法
    • LeetCode 1227 小鸟回笼与飞机座位问题概率分析
    • 基于飞算 JavaAI 的在线教育平台设计与实现
    • C++ 最小生成树详解
    • macOS 升级 Python 3 环境指南:保留系统 Python 2.7
    • C++ 高性能内存池设计与实现
    • C++ 继承进阶:友元、静态成员与菱形继承
    • C++信奥模拟算法习题专项训练
    • Vercel agent-browser 深度解析:AI 驱动浏览器自动化实践
    • Kali Linux 2025 部署与 OpenVAS 安装使用指南
    • Oracle WebLogic 代理插件未授权 RCE 漏洞检测与分析

    相关免费在线工具

    • 加密/解密文本

      使用加密算法(如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