C++算法 二分查找篇(上)

二分查找是算法笔试、面试里最基础也高频考察的算法。它依托有序数组,通过不断折半收缩搜索区间,把线性查找 (O(n)) 的复杂度优化到 (O(log n)) 。但二分有多种变体,很多同学容易混淆**「基础二分」「找左边界」「找右边界」**的写法,一不小心就死循环或者边界出错。我们现在利用四道 LeetCode 经典二分例题,从思路、模拟、代码、复杂度完整梳理,区分不同二分模板,方便理解和背诵。

目录

一、二分查找

[1. 题目](#1. 题目)

​编辑

[2. 核心思路](#2. 核心思路)

[3. 举例模拟](#3. 举例模拟)

[4. 代码参考](#4. 代码参考)

[5. 复杂度](#5. 复杂度)

二、在排序数组中查找元素的第一个和最后一个位置

[1. 题目](#1. 题目)

​编辑

[2. 核心思路](#2. 核心思路)

[3. 举例模拟](#3. 举例模拟)

[4. 代码参考](#4. 代码参考)

[5. 复杂度](#5. 复杂度)

三、X的平方根

[1. 题目](#1. 题目)

​编辑

[2. 核心思路](#2. 核心思路)

[3. 举例模拟](#3. 举例模拟)

[4. 代码参考](#4. 代码参考)

[5. 复杂度](#5. 复杂度)

四、搜索插入位置

[1. 题目](#1. 题目)

​编辑

[2. 核心思路](#2. 核心思路)

[3. 举例模拟](#3. 举例模拟)

[4. 代码参考](#4. 代码参考)

[5. 复杂度](#5. 复杂度)


一、二分查找

1. 题目

2. 核心思路

二分查找,针对有序升序数组,不断缩小查找区间:

  1. 定义左右边界 left、right,代表当前查找范围 [left, right];
  2. 取中间位置 mid,拿 nums[mid] 和目标 target 对比:
    • 相等:直接找到,返回下标 mid;
    • nums[mid] > target:目标一定在左半区间,收缩右边界 right = mid - 1;
    • nums[mid] < target:目标一定在右半区间,收缩左边界 left = mid + 1;
  3. 当 left > right,区间为空,说明不存在目标,返回 -1。

前提:数组单调升序、无重复要求。

3. 举例模拟

示例:nums = [-1,0,3,5,9,12],target = 9 初始:left=0, right=5

第 1 轮:mid=(0+5)/2=2,nums[2]=3 < 9 → left=3

第 2 轮:mid=(3+5)/2=4,nums[4]=9 == 9 → 返回 4,结束

再模拟找不到的情况:target=2 初始:left=0, right=5

mid=2,nums2=3>2 → right=1

mid=0,nums0=-1<2 → left=1

mid=1,nums 1=0<2 → left=2 此时 left=2 > right=1,循环退出,返回 -1

4. 代码参考

cpp 复制代码
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)
            {
                return mid;
            }
            else if(nums[mid]>target)
            {
                right=mid-1;
            }
            else
            {
                left=mid+1;
            }
        }
        return -1;
    }
};

5. 复杂度

时间复杂度:(O(log n)),每次区间折半,n 是数组长度

空间复杂度:(O(1)),只使用常数额外变量,原地二分


二、在排序数组中查找元素的第一个和最后一个位置

1. 题目

2. 核心思路

这道题是二分查找左右边界(在有序数组里找目标值的起始、终止下标),不能只找一个位置。

  1. 数组升序有序,相等元素集中在一起,分两次二分:
    • 第一次二分:找左边界 ,向下取整 mid = left + (right-left)/2。 nums[mid] < target → 左边界在右边 left = mid+1;否则 right=mid,不断往左压缩,锁定第一个等于 target 的位置。
    • 校验:如果最后停下来的位置值不等于 target,说明数组不存在 target,直接返回[-1,-1]。
    • 第二次二分:找右边界 ,向上取整 mid = left + (right-left+1)/2,防止死循环。 nums[mid] <= target → 右边界在右边 left=mid;否则 right=mid-1,往右压缩,锁定最后一个等于 target 的位置。
  2. 两次二分结束,返回左边界和右边界下标。

3. 举例模拟

nums = [5,7,7,8,8,10], target = 8

**找左边界:**left=0, right=5,mid = 0+(5-0)/2 = 2,nums 2=7 <8 → left=3 现在 left=3,right=5 mid = 3+(5-3)/2 =4,nums 4=8 ≥8 → right=4 现在 left=3,right=4 mid=3+(4-3)/2=3,nums 3=8≥8 → right=3 循环结束 left=3,nums 3=8,有效,begin=3

**找右边界:**left=3,right=5 mid=3+(5-3+1)/2 = 4,nums 4=8 ≤8 → left=4 left=4,right=5 mid=4+(5-4+1)/2=5,nums 5=10>8 → right=4 循环结束 right=4

结果: [3,4]

4. 代码参考

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        if(nums.size()==0)
        {
            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;
        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};
    }
};

5. 复杂度

时间复杂度:(O(log n)),两次二分查找,每次都是对数级,n 是数组长度

空间复杂度:(O(1)),只用常数变量,没有额外开辟数组


三、X的平方根

1. 题目

2. 核心思路

  • 我们要找最大的满足 (mid^2 <= x) 的整数 mid ,属于二分找右边界模型,所以 mid 向上取整:mid = left + (right-left+1)/2,避免死循环。
  • 用 long long mid 防止 mid*mid 溢出(int 相乘容易越界)。
  • 判断:
    • 如果 (mid^2 < =x):mid 是候选,答案在 mid 及右侧 → left = mid
    • 如果 (mid^2 > x):mid 太大,答案在左侧 → right = mid -1
  • 循环结束 left == right,就是答案。
  • 特判:(x<=0) 返回 0。

3. 举例模拟

(x=8),目标答案是 2(向下取整) 初始:left=1,right=8

  1. mid = 1+(8-1+1)/2 = 4,(4*=16>8) → right=3 left=1, right=3
  2. mid =1+(3-1+1)/2=2,(2*2=4\le8) → left=2 left=2, right=3
  3. mid =2+(3-2+1)/2=3,(3*3=9>8) → right=2 循环结束 left=right=2,返回 2

4. 代码参考

cpp 复制代码
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)
            {
                left=mid;
            }
            else
            {
                right=mid-1;
            }
        }
        return left;
    }
};

5. 复杂度

时间复杂度:(O(log x)),二分查找

空间复杂度:(O(1)),仅常数变量


四、搜索插入位置

1. 题目

2. 核心思路

要找第一个大于等于 target 的位置(左边界二分模板,向下取整 mid)。

  • 两个前置 if 用来处理插在最前面、插在最后的边界;
  • 二分区间 [0, nums.size()-1];
  • mid = left + (right-left)/2(向下取整)
    • nums[mid] < target:mid 太小,答案在 mid 右边 → left = mid + 1
    • nums[mid] >= target:mid 是候选,答案在 mid 及左边 → right = mid
  • 循环结束 left == right,就是插入位置。

3. 举例模拟

样例:nums=[1,3,5,6], target=2 初始 left=0, right=3

  1. mid=0+(3-0)/2=1,nums1=3 ≥2 → right=1
  2. mid=0+(1-0)/2=0,nums 0=1 <2 → left=1 循环结束 left=1,返回 1

样例:nums=[1,3,3,5], target=3 left=0,right=3

  1. mid=1,nums1=3≥3 → right=1
  2. mid=0,nums 0=1<3 → left=1 返回 1 (正确,第一个 3 的下标)

4. 代码参考

cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) 
    {
        if(target<nums[0])
        {
            return 0;
        }    
        if(target>nums[nums.size()-1])
        {
            return nums.size();
        }
        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;
            }
        }
        return left;
    }
};

5. 复杂度

时间复杂度:(O(log n))

空间复杂度:(O(1))


五、总结

这四道题目覆盖了二分的三类经典写法:

  1. while(left <= right):基础二分,找到直接 return,适合数组无重复、只需要判断是否存在的场景;
  2. while(left < right)、mid 向下取整:左边界模板,用来找第一个满足条件的位置,对应 searchInsert、searchRange 左端点;
  3. while(left < right)、mid 向上取整:右边界模板,用来找最后一个满足条件的位置,对应 mySqrt、searchRange 右端点。

mid 的计算方式决定循环会不会死循环,区间开闭条件、边界收缩方向一定要配套。记住模板不要混用,遇到有序数组查找问题,先判断我们要找**「精确值」、「左边界」还是「右边界」**,再选择对应的二分框架。二分看似简单,但边界是细节重灾区,多模拟样例、理清区间含义,才能稳定写对。

相关推荐
波力海苔夹心脆6751 小时前
C# WPF 资源字典详解:ResourceDictionary、MergedDictionaries 与自定义控件案例
开发语言·c#·wpf·入门教程·resource·资源字典
不会算法的笨小孩2 小时前
异常的使用以及会出现的问题
开发语言·c++·c++11·异常
LabVIEW开发2 小时前
LabVIEW 连接板:28 个端子怎么排,优先级怎么定
java·算法·labview
严肃小白兔3 小时前
MATLAB 数值计算中的精度与误差问题:浮点运算、误差传播与数值稳定性
人工智能·算法·matlab
Hhy_11074 小时前
《C++深度解构06》手撕 string —— 从底层内存管理到深浅拷贝彻底吃透
c语言·开发语言·c++·学习·visual studio
starzy19904 小时前
深入理解Scala函数定义:从方法到函数的类型跨越
开发语言·scala
starzy19904 小时前
深入理解Scala递归函数:从栈溢出到尾调用优化
开发语言·scala
三*一4 小时前
Mapbox‑GL JS 多边形折点编辑工具:实现原理、功能拆解与踩坑总结
开发语言·javascript·ecmascript
青少儿编程课堂4 小时前
蓝桥杯青少组图形化省考报名倒计时·全国青少年信息素养大赛·电子学会图形化考级:校园黑白棋人机对战解析
c++·python·算法·bfs·信息学竞赛