哈喽各位,我是前端小L。
欢迎来到我们的二分查找专题第二篇!上一节(LC 704),我们的任务很简单:找到,或者放弃。if (nums[mid] == target) 是我们最关心的代码。
但今天,target 即使不存在,我们也必须给它"在有序的江湖里找一个位置 "。这个问题,不再是简单的 == 判断,而是要我们找到一个"分界点":
-
这个点左边 的所有元素,都小于
target。 -
这个点及它右边 的所有元素,都大于等于
target。
这个"分界点",就是大名鼎鼎的 lower_bound(C++ STL中的函数),它也是我们"万能模板"的天然归宿。
力扣 35. 搜索插入位置
https://leetcode.cn/problems/search-insert-position/

题目分析: 给定一个排序数组 nums 和一个目标值 target。
-
如果
target存在,返回其索引。 -
如果
target不存在,返回它将会被按顺序插入的索引。 (题目保证数组中无重复元素)
例子:
-
nums = [1, 3, 5, 6],target = 5->target存在,返回2。 -
nums = [1, 3, 5, 6],target = 2->target不存在,应插入到3之前,返回1。 -
nums = [1, 3, 5, 6],target = 7->target不存在,应插入到末尾,返回4。 -
nums = [1, 3, 5, 6],target = 0->target不存在,应插入到开头,返回0。
核心洞察: 仔细观察所有例子,我们发现,我们要找的,其实就是数组中第一个大于或等于 target 的元素的索引。
-
target = 5:第一个>= 5的是5,索引2。 -
target = 2:第一个>= 2的是3,索引1。 -
target = 7:第一个>= 7的元素不存在,此时"插入点"是n(即4)。 -
target = 0:第一个>= 0的是1,索引0。
"万能模板"的真正威力
现在,让我们请出上一节的"万能模板": left = 0, right = n while (left < right) mid = ... if (nums[mid] < target) -> left = mid + 1 else (nums[mid] >= target) -> right = mid
让我们思考一下,当这个循环结束 时(即 left == right 时),left 指向的是什么?
-
left指针(left = mid + 1)会跳过 所有严格小于target的元素。 -
right指针(right = mid)会保留 所有大于或等于target的元素作为"候选答案"。 -
当它们相遇时,
left(或right)所指向的位置,恰好就是第一个nums[i] >= target的位置!
"Aha!"时刻: 我们上一节的"万能模板",根本不需要任何修改 !它本身就是一个完美的 lower_bound(寻找第一个 >= target)的实现!LC 704 中的 if (nums[mid] == target) 只不过是一个"提前退出"的优化。
套用"万能模板"
我们把 LC 704 的代码拿过来,只改动最后返回的部分。
#include <vector>
using namespace std;
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
// 1. 区间定义:[left, right) -> [0, n)
int left = 0;
int right = n;
// 2. 循环条件:left < right
while (left < right) {
// 3. mid 计算
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 目标在右侧 [mid + 1, right)
left = mid + 1;
} else { // nums[mid] >= target
// 目标在左侧 [left, mid),或者 mid 就是答案
right = mid;
}
}
// 循环结束时,left == right
// left 指向的就是第一个 >= target 的位置
return left;
}
};
(注:我们甚至可以把 LC 704 的 if (nums[mid] == target) 判断去掉,逻辑依然完美。)
深度复杂度分析
-
时间复杂度 O(log n):
-
每次循环都将搜索空间
[left, right)缩小一半。 -
寻找一个
n个元素的数组的边界,需要log₂n次。
-
-
空间复杂度 O(1):
- 只使用了
left,right,mid,n等常数个额外变量。
- 只使用了
模板演练(手把手走一遍)
nums = [1, 3, 5, 6], target = 2
-
初始化 :
n = 4,left = 0,right = 4。搜索区间[0, 4)。 -
第1轮:
-
while (0 < 4): True -
mid = 0 + (4 - 0) / 2 = 2 -
nums[2]是5。 -
5 >= 2(nums[mid] >= target)。 -
答案在左侧
[left, mid)。right = mid = 2。 -
新区间
[0, 2)
-
-
第2轮:
-
while (0 < 2): True -
mid = 0 + (2 - 0) / 2 = 1 -
nums[1]是3。 -
3 >= 2(nums[mid] >= target)。 -
答案在左侧
[left, mid)。right = mid = 1。 -
新区间
[0, 1)
-
-
第3轮:
-
while (0 < 1): True -
mid = 0 + (1 - 0) / 2 = 0 -
nums[0]是1。 -
1 < 2(nums[mid] < target)。 -
答案在右侧
[mid + 1, right)。left = mid + 1 = 1。 -
新区间
[1, 1)
-
-
第4轮:
while (1 < 1): False。循环终止。
-
返回
left,即1。完全正确!
总结
今天,我们领略了"万能模板"在处理边界查找 时的惊人威力。 我们甚至不需要修改它,它天生就是为 lower_bound(寻找第一个 >= target)而设计的。
-
left = mid + 1:是因为我们确定mid以及mid左侧的所有元素都不是答案。 -
right = mid:是因为mid可能 是答案(第一个>= target的),我们不能 把它排除掉(mid-1),只能将搜索上界缩到mid。
掌握了这个思想,下一题,我们将用它来解决一个更棘手的问题:在一个有重复元素的数组中,寻找目标的"第一个"和"最后一个"位置。
下期见!