cpp二分库函数 lambda捕获

文章目录

  • [二分库函数 ranges::lower_bound](#二分库函数 ranges::lower_bound)
      • [1. 最直观的对比](#1. 最直观的对比)
      • [2. 在 LIS 中的完整用法](#2. 在 LIS 中的完整用法)
      • [3. 为什么叫 "lower_bound"?](#3. 为什么叫 "lower_bound"?)
      • [4. 相比旧版 `std::lower_bound` 的优势](#4. 相比旧版 std::lower_bound 的优势)
        • [💡 投影功能(高级但实用)](#💡 投影功能(高级但实用))
      • [5. ⚠️ 面试注意事项](#5. ⚠️ 面试注意事项)
      • [📌 一句话总结](#📌 一句话总结)
  • [捕获 lambda【&】](#捕获 lambda【&】)
      • [1. 三种最常见的捕获方式对比](#1. 三种最常见的捕获方式对比)
      • [2. 用一张表记住](#2. 用一张表记住)
      • [3. 为什么面试建议用 `\&ls` 而非 `\&`?](#3. 为什么面试建议用 [&ls] 而非 [&]?)
      • [4. ⚠️ `\&` 的经典坑](#4. ⚠️ [&] 的经典坑)
      • [📌 一句话总结](#📌 一句话总结)

二分库函数 ranges::lower_bound

ranges::lower_bound 是 C++20 引入的 <algorithm> 库中的函数,它是传统 std::lower_bound 的现代升级版。

别被名字吓到,它的核心功能非常简单:在一个有序序列中,二分查找第一个"大于等于"目标值的位置。

1. 最直观的对比

假设你有一个严格递增数组 g = [2, 4, 6, 8],你要找 x = 5:

cpp 复制代码
// ✅ C++20 新写法(简洁、安全)
auto it = ranges::lower_bound(g, 5);
// it 指向 6(第一个 >= 5 的元素)

// ❌ C++17 旧写法(冗长、易错)
auto it = std::lower_bound(g.begin(), g.end(), 5);
// 效果完全相同,但要手写 begin/end

如果 x = 9(比所有元素都大),it == g.end(),表示没找到。

2. 在 LIS 中的完整用法

回到你之前的代码,逐行拆解:

cpp 复制代码
vector<int> g; // g 始终保持严格递增

for (int x : nums) {
    // 🔍 在 g 中二分查找第一个 >= x 的位置
    auto it = ranges::lower_bound(g, x);

    if (it == g.end()) {
        // x 比 g 中所有元素都大 → LIS 可以延长
        g.push_back(x);
    } else {
        // 找到了 >= x 的元素 → 用更小的 x 替换它
        // 这样未来有更多机会延长 LIS
        *it = x;
    }
}
return g.size(); // g 的长度就是 LIS 长度

3. 为什么叫 "lower_bound"?

这个名字来源于数学概念:

术语 含义 对应 STL 函数
Lower Bound 第一个 ≥ target 的位置 ranges::lower_bound
Upper Bound 第一个 > target 的位置 ranges::upper_bound

记忆口诀:

  • Lower = ≥ (下界,包含自身)
  • Upper = > (上界,不包含自身)

在 严格递增 LIS 中,我们用 lower_bound(因为相等元素不能延长 LIS,需要替换)。如果题目改为非递减 LIS ,则改用 upper_bound。

4. 相比旧版 std::lower_bound 的优势

特性 std::lower_bound (C++98) ranges::lower_bound (C++20)
传参方式 必须传 begin, end 迭代器对 直接传容器/range
安全性 容易传错 begin/end 导致 UB 编译期检查,不可能传错
可读性 lower_bound(v.begin(), v.end(), x) ranges::lower_bound(v, x)
投影支持 不支持 ✅ 支持第三参数自定义比较
时间复杂度 O(log N) O(log N)(完全相同)
💡 投影功能(高级但实用)
cpp 复制代码
struct Student { int score; string name; };
vector<Student> students = {{90,"A"}, {80,"B"}, {70,"C"}};
// 按 score 排序后,直接按 score 字段二分查找
auto it = ranges::lower_bound(students, 85, {}, &Student::score);
// 无需写 lambda,第三个参数 {} 表示默认比较,第四个是投影

5. ⚠️ 面试注意事项

场景 建议
面试官允许 C++20 ✅ 放心用 ranges::lower_bound
不确定 C++ 版本 ⚠️ 用 std::lower_bound(g.begin(), g.end(), x)
面试官问区别 回答:"语义相同,ranges 版本更安全、更简洁,是 C++20 的改进"
手撕二分环节 ✍️ 两者都不要用,手写标准二分模板

📌 一句话总结

ranges::lower_bound(container, value) = 在有序容器中二分找第一个 ≥ value 的位置 。它就是 std::lower_bound 的语法糖 + 安全加固版,LIS 中用它来维护贪心数组,把 O ( N 2 ) O(N^2) O(N2) 优化到 O ( N log ⁡ N ) O(N \log N) O(NlogN)。

如果你当前环境不支持 C++20,记住等价替换即可,逻辑完全一致:

cpp 复制代码
// C++20
auto it = ranges::lower_bound(g, x);

// C++17 及以下(完全等价)
auto it = lower_bound(g.begin(), g.end(), x);

Lower = ≥ (下界)

Upper = > (上界)

想要 < → 用 lower_bound 然后 -1

想要 ≤ → 用 upper_bound 然后 -1

永远记得检查边界(it == begin() 时减一会越界!)

你想找的 数学含义 STL 函数 C++20 写法 C++17 写法
第一个 ≥ x lower bound lower_bound ranges::lower_bound(g, x) std::lower_bound(g.begin(), g.end(), x)
第一个 > x upper bound upper_bound ranges::upper_bound(g, x) std::upper_bound(g.begin(), g.end(), x)
第一个 < x ❌ 无直接函数 用 lower_bound 反推 见下方 👇 见下方 👇
第一个 ≤ x ❌ 无直接函数 用 upper_bound 反推 见下方 👇 见下方 👇

捕获 lambda【&】

在 C++ 的 Lambda 表达式中,[] 里的内容叫做捕获列表(Capture List),它决定了 Lambda 如何访问外部作用域的变量。

[&] 的意思是:按引用(by reference)捕获外部所有自动存储期变量。

1. 三种最常见的捕获方式对比

cpp 复制代码
int a = 10;
int b = 20;

// ✅ [&]  按引用捕获所有外部变量
auto f1 = [&]() { a++; };      // 修改的是外部的 a
f1();
cout << a;                     // 输出 11 ← 外部 a 被改了

// ✅ [=]  按值(拷贝)捕获所有外部变量
auto f2 = [=]() mutable { a++; }; // 修改的是内部副本
f2();
cout << a;                     // 输出 10 ← 外部 a 不变

// ✅ [&ls] 只按引用捕获 ls,其他变量不可访问
auto f3 = [&ls](int x) { ... };   // 精确控制,面试推荐

2. 用一张表记住

写法 含义 能否修改外部变量 安全性
[&] 按引用捕获所有 ✅ 能 ⚠️ 低:可能意外修改不该改的变量
[=] 按值拷贝所有 ❌ 不能(除非加 mutable) ⚠️ 中:大对象拷贝有性能开销
[&var] 只按引用捕获 var ✅ 仅 var 能 ✅ 高:意图明确
[var] 只按值拷贝 var ❌ 不能 ✅ 高
[this] 捕获当前对象指针 ✅ 通过 this-> ⚠️ 注意生命周期
[&a, b] 混合:a 引用,b 拷贝 仅 a 能 ✅ 高

3. 为什么面试建议用 [&ls] 而非 [&]?

回到你的 LIS 代码:

cpp 复制代码
// ❌ [&] ------ 捕获了 nums, n, i, findindex 等所有变量
//    面试官会想:"你真的需要捕获这么多吗?还是只是懒得写?"
auto findindex = [&](int x) -> int { ... };

// ✅ [&ls] ------ 只捕获了二分查找真正需要的数组
//    面试官看到就知道:你清楚这个 lambda 只依赖 ls
auto findIndex = [&ls](int x) -> int { ... };

核心区别不是功能,而是表达力:显式捕获 = 告诉面试官"我思考过这个 Lambda 的数据依赖边界"。

4. ⚠️ [&] 的经典坑

cpp 复制代码
vector<function<int()>> funcs;
for (int i = 0; i < 5; i++) {
    funcs.push_back([&]() { return i; }); // 💥 按引用捕获了循环变量 i
}
for (auto& f : funcs) cout << f() << " ";
// 输出:5 5 5 5 5 (全是 5!因为 i 已经变成 5 了)

如果改成 [i](按值捕获),每个 Lambda 会保存自己那份 i 的副本,输出就是 0 1 2 3 4。

📌 一句话总结

[&] = "把外面所有变量都借进来用(可改)"

[&ls] = "我只借 ls 这一个变量来用"

功能上 [&] 没问题,但面试中写 [&ls] 是在展示你对数据流的精确掌控。

相关推荐
longlongzihan1 小时前
LeetCode 76. 最小覆盖子串:滑动窗口算法详解
算法·leetcode·滑动窗口
怕浪猫10 小时前
RAG 面试 6 连问,从原理到优化全部覆盖
python·算法·面试
无线通信科研笔记10 小时前
IEEE TVT 2026 论文精读与完整复现|相位误差如何重塑近场 RIS 的幅相响应
论文阅读·人工智能·python·算法·论文笔记
mmmmath_311 小时前
二叉树の递归遍历
算法
All for pursuit.11 小时前
【位运算-1】338.比特位计数
数据结构·c++·算法·leetcode
小师兄吃牛肉12 小时前
C语言中的nullptr有必要吗
c语言·c++·算法
一只小小的芙厨14 小时前
【线性DP】
学习·算法·动态规划
奋发向前wcx14 小时前
CSP-J复赛模拟赛5 王晨旭补题 2026.10.5
算法
罗湖老棍子15 小时前
C Looooops(信息学奥赛一本通- P1640)
算法·数论·同余·裴蜀定理·扩展欧几里得