<p>第一个小于 target 的位置等价于 std::lower_bound 查找 target 时返回的迭代器减 1,即 std::lower_bound(first, last, target) - 1,前提是该位置存在且不越界。</p>

std::lower_bound 不能直接用,得改逻辑
标准库的 std::lower_bound 找的是第一个不小于目标值的位置(即 ≥ target),而你需要的是第一个小于 target 的位置(即 std::lower_bound(...)-1 在 target 小于所有元素时会越界,返回非法迭代器。
正确做法是复用二分结构,但调整比较条件和边界更新逻辑:
- 当
*mid :说明 mid 位置合法,且可能不是最右的一个,所以保留 mid,并往右找 → <code>left = mid(注意不是mid + 1) - 当
*mid >= target:mid 不合法,必须排除 →right = mid - 1 - 循环条件用
left ,且采用向上取整的中点: <code>mid = left + (right - left + 1) / 2,避免死循环
手写模板函数要小心整数溢出和迭代器失效
如果封装成泛型函数,别直接用 (left + right) / 2 算中点——对指针或随机访问迭代器,left + right 可能溢出(尤其 ptrdiff_t 较小时)。应统一用 left + (right - left) / 2 或带 +1 的变体。
另外,返回位置需明确语义:若所有元素都 ≥ target,应返回 first(即范围起始,表示“没有小于 target 的元素”);若所有元素都 last - 1。常见错误是返回 last 或未处理空范围。
立即学习“C++免费学习笔记(深入)”;
简短示例(数组场景):
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
int lower_than(int arr[], int n, int target) {
int l = 0, r = n - 1;
int res = -1; // 表示未找到
while (l <= r) {
int m = l + (r - l) / 2;
if (arr[m] < target) {
res = m; // 记录候选
l = m + 1; // 尝试找更右的
} else {
r = m - 1;
}
}
return res; // 返回最右一个小于 target 的下标,-1 表示无
}用 std::upper_bound 间接实现更安全
其实有个绕法:第一个小于 target 的位置,等价于 std::upper_bound(first, last, target - 1) 的结果 —— 因为 std::upper_bound(..., x) 返回第一个 > x 的位置,而 > (target - 1) 就等价于 ≥ target,不对。
等等,这思路错了。再想:第一个
真正可用的间接方式是:先用 std::lower_bound 找到第一个 ≥ target 的位置,然后往前跳一步。但必须加保护:
- 若
it == first,说明没有元素 first - 否则返回
--it,即前一个位置
这个方法简洁、复用标准行为,且避免手写二分的边界 bug,推荐在容器支持随机访问迭代器时优先采用。
vector 和 array 场景下性能差异几乎为零
无论是手写循环还是调 std::lower_bound 后退一步,编译器对 std::vector::iterator 或原生数组指针的优化都很到位,O(log n) 时间、无额外分配。但要注意:如果传入的是 std::list 迭代器,std::lower_bound 退化为 O(n),此时不如直接线性扫描——二分只在随机访问成立时有意义。
另一个容易被忽略的点:浮点数比较时, 和 <code>== 的精度问题会影响“第一个小于”的判定边界。如果 target 是 float/double,建议用 std::abs(a - b) 替代直接比较,但这时已不属于严格二分范畴,需重审需求。

















