std::lower_bound和std::upper_bound可直接求目标值左右边界;前者返回首个≥target迭代器,后者返回首个>target迭代器,差值即出现次数,区间左闭右开。

用 std::lower_bound 和 std::upper_bound 直接解决
标准库已经提供了两个精准匹配的函数,不需要手写二分。只要数组是升序(std::sort 后或天然有序),就能在 O(log n) 时间内拿到左右边界。
关键点:
- std::lower_bound 返回第一个 ≥ target 的迭代器
- std::upper_bound 返回第一个 > target 的迭代器
- 两者差值就是 target 出现次数,且区间左闭右开
示例:
vector<int> nums = {5,7,7,8,8,10};
int target = 8;
auto left = lower_bound(nums.begin(), nums.end(), target);
auto right = upper_bound(nums.begin(), nums.end(), target);
if (left == right) {
// 没找到,返回 [-1,-1]
} else {
int first = left - nums.begin();
int last = right - nums.begin() - 1; // 注意减1,因为 upper_bound 是开区间右端
}</int>
手写二分时为什么不能只改一个判断条件
很多人想复用同一个二分模板,只改 或 <code>,结果边界出错。根本原因是:查找“第一个”和“最后一个”本质是两个不同语义的搜索问题,终止条件和收缩方向必须独立设计。
常见错误现象:
- 找第一个位置时,nums[mid] == target 后却向右缩(应向左缩)
- 找最后一个位置时,没处理 mid 落在重复段中间导致漏掉右边界
- 忘记检查 left 是否越界(比如 target 比所有数都大)
正确做法:
- 查第一个:满足 nums[mid] >= target 时,right = mid(不减1,保留 mid)
- 查最后一个:满足 nums[mid] 时,<code>left = mid + 1(主动跳过 mid,最后回退)
- 两次二分后,都必须验证 nums[left] 或 nums[right] 确实等于 target
输入为空或全不匹配时的边界检查怎么做
空数组、target 小于最小值、大于最大值——这三种情况都会导致 lower_bound 返回 end(),或者手写二分后 left >= size() 或 nums[left] != target。
必须显式判断,不能只靠索引是否在 [0, n) 内:
- lower_bound 返回 nums.end() → 说明所有元素都 *lower_bound(...) != target → 说明不存在
- 同理,upper_bound 返回 begin() 也不代表有解,得先确认 lower_bound 有效
所以安全写法是:
- 先算 left_it = lower_bound(...)
- 再判断 left_it == nums.end() || *left_it != target
- 只有通过才继续算 right_it 并构造结果
用 std::equal_range 一步到位但要注意返回类型
它其实是 lower_bound 和 upper_bound 的封装,返回 pair<iterator iterator></iterator>,语义更清晰。
但容易踩的坑:
- 返回的是迭代器对,不是下标,要手动减 begin()
- 如果没找到,first == second,此时不能直接取 *first(解引用非法)
- 它不支持自定义比较器以外的逻辑(比如降序数组需传 greater<int>()</int>)
简短示例:
auto range = equal_range(nums.begin(), nums.end(), target);
if (range.first == range.second) {
return {-1, -1};
}
return {range.first - nums.begin(), range.second - nums.begin() - 1};
真正复杂的地方不在算法本身,而在于每次调用后都得做一次存在性校验——少一次判断,就可能解引用 end() 或越界访问。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











