std::lower_bound更值得优先使用,因其经标准库充分测试、支持所有随机访问迭代器、自动处理边界与空容器,避免手写易出的终止条件遗漏和整数溢出问题。

std::lower_bound 为什么比手写二分更值得优先用
绝大多数场景下,直接调用 std::lower_bound 是最稳妥的选择——它经过标准库充分测试,支持所有随机访问迭代器(vector、array、原生数组),且自动处理边界和空容器。自己写循环容易漏掉 left == right 的终止条件判断,或在 mid 计算时整数溢出(尤其当 left 和 right 很大时)。
常见错误现象:std::lower_bound 返回 end() 却没检查,直接解引用导致崩溃;或者误以为它返回的是“是否找到”,其实它只返回插入位置。
- 使用场景:查找第一个 ≥ 目标值的位置(即左边界),适用于去重、统计频次、区间合并等
- 参数差异:第三个参数是
value,第四个可选参数是自定义比较函数,比如std::greater<int>()</int>可用于降序数组 - 性能影响:O(log n),和手写一致;但编译器对标准算法更容易内联和优化
- 示例:
vector<int> arr = {1,2,2,2,4,5};<br>auto it = std::lower_bound(arr.begin(), arr.end(), 2);<br>// it 指向第一个 2,即 arr[1]
手写二分时 left 还是 <code>left
取决于你想要的语义和循环退出后如何处理结果。用 left 更直观:每次迭代都覆盖一个闭区间,循环结束时 <code>left > right,意味着没找到;而 left 维护的是左闭右开区间,退出时 <code>left == right,需额外判断是否命中。
容易踩的坑:两种写法混用却没同步调整 mid 更新逻辑或边界赋值方式,导致死循环或漏查一个元素。
立即学习“C++免费学习笔记(深入)”;
- 推荐统一用
left ,尤其新手:边界清晰,<code>mid计算用left + (right - left) / 2避免溢出 - 更新
right时别写成right = mid(会漏掉mid),应为right = mid - 1 - 更新
left时同理,用left = mid + 1,而非left = mid - 示例(查找是否存在):
int l = 0, r = arr.size() - 1;<br>while (l <= r) {<br> int mid = l + (r - l) / 2;<br> if (arr[mid] == target) return mid;<br> else if (arr[mid] < target) l = mid + 1;<br> else r = mid - 1;<br>}<br>return -1;
数组不是 std::vector,怎么用 std::lower_bound
可以,只要提供随机访问迭代器。原生数组名本身不是迭代器,但可以用 &arr[0] 和 &arr[n] 构造指针迭代器——C++ 中指针天然满足随机访问迭代器要求。
常见错误现象:传入 arr(退化为指针)和 arr + n 时,忘记 n 是数组长度,导致越界;或对未排序数组调用,行为未定义。
- 使用场景:嵌入式、性能敏感代码中避免容器开销,或对接 C 接口的数组
- 参数差异:第一个参数是
&arr[0]或arr(等价),第二个是&arr[n],不能是arr + n - 1 - 兼容性影响:C++11 起完全支持;注意
arr必须是已知大小的栈数组,不能是函数参数接收的int* - 示例:
int arr[] = {1,3,5,7,9};<br>size_t n = sizeof(arr)/sizeof(arr[0]);<br>auto it = std::lower_bound(&arr[0], &arr[n], 5); // 找到 5
重复元素多时,std::lower_bound 和 std::upper_bound 配合用
单靠 std::lower_bound 只能定位左边界;要获取所有等于目标值的范围,必须搭配 std::upper_bound——它返回第一个 > 目标值的位置。两者差值就是重复个数,区间就是完整匹配段。
容易被忽略的地方:很多人只记得 lower_bound,忘了 upper_bound 参数签名完全一致,只是语义不同;也有人误用 equal_range 却不拆包,其实它返回的是 pair<iterator iterator></iterator>,本质就是调用了这两个函数。
- 使用场景:统计出现次数、批量修改相同值、实现 multiset-like 行为
- 性能影响:两次 O(log n) 查询,仍优于线性扫描;
equal_range内部也是这么做的,但少一次函数调用开销 - 示例:
auto [l, r] = std::equal_range(arr.begin(), arr.end(), 2);<br>int count = r - l; // 等价于 std::upper_bound(...) - std::lower_bound(...)
事情说清了就结束。边界条件、溢出、迭代器有效性——这些地方一松懈,二分就从“高效”变成“玄学”。










