二分搜索找第一个小于target的元素本质是定位target插入点左侧位置;推荐左闭右闭区间、left初始化为-1,循环中mid需上取整避免死循环,空数组或target最小则返回-1。

二分搜索找第一个小于 target 的元素,本质是找右边界左侧位置
直接结论:这不是标准二分的“等于”或“大于等于”场景,而是要定位 target 的**插入点左侧**——即所有 nums[i] 中最大的索引 <code>i。关键不是“第一个”,而是“最靠右的那个满足条件的”。
常见误解是套用 lower_bound 或手动写“找第一个 >= target”的逻辑再减一,但边界越界、空数组、全 >= target 等情况极易出错。
手写循环版本:用左闭右闭区间,left 初始为 -1
推荐初始化 left = -1、right = nums.size()(左开右开),但更直观且不易漏的是左闭右闭 + left 从 -1 开始,这样最终 left 自然停在最后一个合法位置:
- 循环条件用
left ,每次更新后区间仍有效 - 当
nums[mid] ,说明 <code>mid可能是答案,且答案可能在右侧 →left = mid(不是mid + 1) - 否则
right = mid - 1 - 退出时
left即所求索引;若left == -1,说明没有元素 target
int lower_than_target(const vector<int>& nums, int target) {
int left = -1, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2; // 上取整,避免死循环
if (nums[mid] < target) {
left = mid;
} else {
right = mid - 1;
}
}
return left; // 可能为 -1
}
用 std::lower_bound 推导:减一后必须检查有效性
std::lower_bound 返回第一个 >= target 的迭代器,它左边那个位置才可能是最后一个 target 的元素。但直接减一有风险:
立即学习“C++免费学习笔记(深入)”;
- 若
lower_bound返回begin(),说明所有元素都 >=target,此时减一越界 - 若数组为空,
begin()==end(),减一非法 - 正确做法:先判断返回位置是否为
begin(),是则返回 -1;否则返回it - begin() - 1
int using_lower_bound(const vector<int>& nums, int target) {
auto it = lower_bound(nums.begin(), nums.end(), target);
if (it == nums.begin()) return -1;
return it - nums.begin() - 1;
}
容易踩的坑:上取整、越界、空数组、重复值处理
手写时最容易卡在 mid 计算方式上:用 (left + right) / 2 在 left = mid 分支下会陷入死循环(比如 [0,1],mid 始终为 0)。必须用上取整:left + (right - left + 1) / 2。
另外,即使数组有序,也要确认是否允许空输入;target 比所有元素都小或都大时,返回值语义必须明确(-1 还是 0?);重复值不影响逻辑,因为只关心大小关系,不依赖唯一性。
实际用的时候,别硬记“第一个小于”,先想清楚你要的是插入位置的前一个索引——这个视角比字面描述更稳。


















