二分搜索算法的介绍和使用

文章目录

一、什么是二分搜索

二分搜索(Binary Search)是一种在有序数组/区间中查找目标值的位置的算法。

核心思想:每次把搜索范围缩小一半,从而快速定位数据。范围从 n 缩小到 n/2 → n/4 → n/8...

时间复杂度就是:

cpp 复制代码
O(log n)

二、二分搜索的适用条件

必须满足两个条件:

  1. 数据有序(升序或降序)
  2. 可以通过下标随机访问(数组、vector 等)

不适用于链表(随机访问 O(n))

三、二分搜索的工作流程

示例:在数组中查找 target = 9

数组(升序):

cpp 复制代码
索引: 0  1  2  3  4  5   6
数据: 1  3  5  7  9  11  15

初始区间:

cpp 复制代码
l = 0
r = 6

第 1 次查找

cpp 复制代码
区间: [0........................................6]
数据:  1  3  5  7  9  11  15
             ^ mid = 3  (值 = 7)

判断:

cpp 复制代码
nums[mid] = 7 < 9
→ 目标在右侧
→ l = mid + 1 = 4

第 2 次查找

cpp 复制代码
区间: [4....................6]
数据:  1  3  5  7  9  11  15
                    ^ mid = 5 (值 = 11)

判断:

cpp 复制代码
nums[mid] = 11 > 9
→ 目标在左侧
→ r = mid - 1 = 4

第 3 次查找

cpp 复制代码
区间: [4]
数据:  1  3  5  7  9  11  15
                ^ mid = 4 (值 = 9)

判断:

cpp 复制代码
nums[mid] == target → 找到目标

整个过程的如下展示:

cpp 复制代码
Step 1:
[1 3 5 7 9 11 15]
 l           m     r
 0           3     6

Step 2:
[1 3 5 7 9 11 15]
         l     m   r
         4     5   6

Step 3:
[1 3 5 7 9 11 15]
             l/m/r
             4

更形象的"半区排除图"

cpp 复制代码
开始:
[ 1  3  5  7 | 9  11  15 ]
               ↑7<9 → 左半区丢弃

继续:
[ 9 | 11  15 ]
      ↑11>9 → 右侧丢弃

最终:
[ 9 ]
  ↑ 找到!

四、二分搜索常见错误

中间点写成 (l + r) / 2 可能溢出

建议:

cpp 复制代码
mid = l + (r - l) / 2;

while 条件写成 < 或 <= 搞混

闭区间写法必须:

cpp 复制代码
while (l <= r)

开区间写法:

cpp 复制代码
while (l < r)

不要死循环

如:

cpp 复制代码
mid = (l + r) / 2;
// 然后没有改变 l 或 r

一定要注意每次都要缩小范围。

五、二分搜索的代码模板(升序)

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

// 标准二分:查找 target
int binarySearch(const vector<int>& nums, int target) {
    int l = 0, r = nums.size() - 1;

    while (l <= r) {
        int mid = l + (r - l) / 2;

        if (nums[mid] == target)
            return mid;
        else if (nums[mid] < target)
            l = mid + 1;
        else
            r = mid - 1;
    }
    return -1;
}

int main() {
    vector<int> nums = {1, 3, 5, 7, 9, 11};

    cout << binarySearch(nums, 7) << endl; // 输出 3
    
    return 0;
}

为什么不能用 mid = (l+r)/2?

因为会发生「整数溢出」(overflow)

假设:

cpp 复制代码
int l = 1_000_000_000;
int r = 2_000_000_000;

两者都在 int 可表示范围内,但你求 mid 时:

cpp 复制代码
int mid = (l + r) / 2;  // 可能溢出

l + r = 3_000_000_000,已经超过 int 最大值(2_147_483_647)

产生溢出 → mid 变成负数或奇怪值,程序会直接二分错位甚至死循环。

所以才有了 官方推荐写法:

cpp 复制代码
int mid = l + (r - l) / 2;

这样 r - l 永远不会超出范围(最大约 2e9),避免溢出。

图示解释(直观)

假设区间 [l, r] = [10, 20]

错误写法:

cpp 复制代码
 mid = (10 + 20) / 2 = 15   // ✔ 正常

但当边界巨大:

cpp 复制代码
 l → 非常大
 r → 非常大
 l + r → 超过 int 上限 → ❌ 溢出

正确写法:

cpp 复制代码
 mid = l + (r - l) / 2

因为 (r - l) 很小,所以不会超出范围

结论:不能使用 (l + r) / 2 的主要原因是:当 l 和 r 足够大时,l + r 可能产生整数溢出,导致 mid 计算错误。而 l + (r - l) / 2 不会溢出,因此是安全写法。

六、查找第一个 >= target

在有序数组中查找:

cpp 复制代码
第一个 >= target 的位置

假设:

cpp 复制代码
数组: [1, 3, 5, 7, 9, 11, 15]
target = 8

正确答案应该是元素 9(index = 4),因为它是第一个 ≥ 8 的数字。

使用的二分区间写法(典型的 lower_bound 写法)

cpp 复制代码
l = 0
r = n   // r 是开区间,不可取
while (l < r)

mid = (l + r) / 2

如果 nums[mid] >= target → 可能是答案 → 缩 r

否则 → mid 太小 → 缩 l

图形演示过程:

cpp 复制代码
Step 1:
[1 3 5 7 9 11 15]
 l           m      r
 0           3      7
(7 < 8 → 左半废)

Step 2:
[1 3 5 7 9 11 15]
             l   m   r
             4   5   7
(11 >= 8 → 右半废)

Step 3:
[1 3 5 7 9 11 15]
             l/m r
             4   5
(9 >= 8 → 右半废)

Step 4:
[1 3 5 7 9 11 15]
             l=r=4

C++ 版本(对应这个图示)

cpp 复制代码
int lowerBound(const vector<int>& nums, int target) {
    int l = 0, r = nums.size(); // 开区间

    while (l < r) {
        int mid = l + (r - l) / 2;

        if (nums[mid] >= target)
            r = mid;       // mid 可能是答案
        else
            l = mid + 1;   // mid 太小
    }

    return l;  // 第一个 >= target 的位置
}

七、查找最后一个 <= target

使用的二分模板(闭区间版本)

要找 "最后一个 ≤ target",一般用 闭区间 [l, r] 写法:

cpp 复制代码
while (l <= r):
    mid = ...
    if nums[mid] <= target:
         ans = mid      // 可能是答案
         l = mid + 1    // 尝试往右找更大的
    else:
         r = mid - 1

图形演示过程:

cpp 复制代码
Step 1:
[1 3 5 7 9 11 15]
 l       m       r
 0       3       6
(7 <= 8 → 候选,往右找)


Step 2:
[1 3 5 7 9 11 15]
         l   m   r
         4   5   6
(11 > 8 → 往左)


Step 3:
[1 3 5 7 9 11 15]
         l/m r
         4   4
(9 > 8 → 往左)


Step 4:
[1 3 5 7 9 11 15]
         r   l
         3   4
结束 → r=3

完整 C++ 代码(对应图示)

cpp 复制代码
int lastLessEqual(const vector<int>& nums, int target) {
    int l = 0, r = nums.size() - 1;
    int ans = -1;

    while (l <= r) {
        int mid = l + (r - l) / 2;

        if (nums[mid] <= target) {
            ans = mid;     // 记录候选
            l = mid + 1;   // 去右边找更大的
        } else {
            r = mid - 1;
        }
    }
    return ans; // 返回最后一次记录的位置
}
相关推荐
一起养小猫2 小时前
LeetCode100天Day4-盛最多水的容器与两数之和II
java·数据结构·算法·leetcode
xie_pin_an2 小时前
深入解析 C 语言排序算法:从快排优化到外排序实现
c语言·算法·排序算法
Hcoco_me2 小时前
机器学习核心概念与主流算法(通俗详细版)
人工智能·算法·机器学习·数据挖掘·聚类
Hcoco_me2 小时前
嵌入式场景算法轻量化部署checklist
算法
咸鱼加辣2 小时前
【python面试】Python 的 lambda
javascript·python·算法
Jerryhut2 小时前
sklearn函数总结十二 —— 聚类分析算法K-Means
算法·kmeans·sklearn
Swift社区2 小时前
LeetCode 453 - 最小操作次数使数组元素相等
算法·leetcode·职场和发展
hoiii1872 小时前
LR算法辅助的MIMO系统Zero Forcing检测
算法
糖葫芦君2 小时前
Lora模型微调
人工智能·算法