📌 题目链接:35. 搜索插入位置 - 力扣(LeetCode)
🔍 难度:简单 | 🏷️ 标签:数组、二分查找
⏱️ 目标时间复杂度:O(log n)
💾 空间复杂度:O(1)
🔍 题目分析
给定一个 无重复元素的升序排列数组 nums 和一个目标值 target,要求:
- 如果
target存在于数组中,返回其索引; - 如果不存在,则返回它按顺序插入的位置。
关键约束条件是:必须使用时间复杂度为 O(log n) 的算法。这意味着暴力遍历(O(n))不被接受,必须使用二分查找(Binary Search) 。
💡 面试考点提示:本题看似简单,但考察的是对「二分查找边界处理」的深刻理解。很多同学能写出查找存在的元素,但在“找不到时返回插入位置”这一变种上容易出错。这正是面试官喜欢问的细节!
🧠 核心算法及代码讲解:二分查找(左闭右闭 + 上界查找)
本题的核心在于将问题转化为:在有序数组中找到第一个大于等于 target 的元素下标。
这个思想称为 “lower_bound” (下界查找),是 C++ STL 中 std::lower_bound 的实现逻辑,也是二分查找的经典变种之一。
✅ 为什么可以这样转化?
- 若
target存在,第一个 ≥target的位置就是它本身; - 若
target不存在,该位置就是它应该插入的位置(保持升序)。
例如:
nums = [1,3,5,6], target = 2→ 第一个 ≥2 的是3(index=1)→ 插入位置为 1 ✅target = 7→ 所有元素都 <7 → 应插入末尾(index=4)✅
📜 二分查找模板选择:左闭右闭 + 提前记录 ans
我们采用经典的 左闭右闭区间 [left, right] ,并在每次 nums[mid] >= target 时更新答案 ans = mid,然后继续向左搜索(right = mid - 1),以寻找更靠左的满足条件的位置。
🎯 关键技巧:初始化
ans = n(数组长度),这样当target大于所有元素时,直接返回n,无需额外判断边界!
💻 C++ 核心算法代码(带逐行注释)
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
int left = 0, right = n - 1;
int ans = n; // 初始化为数组长度,处理 target > 所有元素的情况
while (left <= right) { // 左闭右闭区间,循环条件为 left <= right
int mid = ((right - left) >> 1) + left; // 防止 (left + right) 溢出,等价于 (left + right) / 2
if (target <= nums[mid]) { // 找到一个候选位置:mid 可能是答案
ans = mid; // 记录当前可能的插入位置
right = mid - 1; // 继续向左搜索,看是否有更小的合法位置
} else {
left = mid + 1; // 当前 mid 太小,去右半部分找
}
}
return ans; // 最终 ans 即为第一个 >= target 的下标
}
⚠️ 注意位运算写法:
((right - left) >> 1) + left是安全的中点计算方式,避免left + right在大数时溢出(虽然本题数据范围不会,但这是工程好习惯!)
🧩 解题思路(分步拆解)
-
明确问题本质
不是简单“找是否存在”,而是“找第一个 ≥ target 的位置”——这是二分查找的典型应用场景。 -
确定搜索区间与终止条件
使用[left, right]闭区间,当left > right时结束,此时ans已记录最优解。 -
处理边界情况
target小于所有元素 → 返回 0;target大于所有元素 → 返回n;- 通过初始化
ans = n自动覆盖后者,前者由二分过程自然得出。
-
更新策略
- 当
nums[mid] >= target:当前位置可能是答案,但左边可能还有更优解 → 记录ans = mid,并right = mid - 1; - 否则:
left = mid + 1。
- 当
-
返回结果
循环结束后,ans即为所求插入位置。
📊 算法分析
| 项目 | 分析 |
|---|---|
| 时间复杂度 | O(log n) :每次循环将搜索空间减半,最多 log₂n 次迭代 |
| 空间复杂度 | O(1) :仅使用常数个变量(left, right, mid, ans) |
| 稳定性 | 稳定,无递归,无额外空间 |
| 适用场景 | 有序数组的查找、插入位置、上下界查询等 |
💼 面试加分点:
- 能说出这是
lower_bound的实现;- 能对比
upper_bound(第一个 > target 的位置);- 能手写三种二分模板(左闭右闭、左闭右开、红蓝染色法);
- 能解释为何不用
left + right >> 1(溢出风险)。
💻 完整代码实现
✅ C++ 版本
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
int left = 0, right = n - 1, ans = n;
while (left <= right) {
int mid = ((right - left) >> 1) + left;
if (target <= nums[mid]) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
Solution sol;
vector<int> nums1 = {1,3,5,6};
cout << sol.searchInsert(nums1, 5) << "\n"; // 输出: 2
cout << sol.searchInsert(nums1, 2) << "\n"; // 输出: 1
cout << sol.searchInsert(nums1, 7) << "\n"; // 输出: 4
cout << sol.searchInsert({1}, 0) << "\n"; // 输出: 0
return 0;
}
✅ JavaScript 版本
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
const n = nums.length;
let left = 0, right = n - 1;
let ans = n; // 初始化为数组长度
while (left <= right) {
const mid = Math.floor((right - left) / 2) + left;
if (target <= nums[mid]) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
};
// 测试
console.log(searchInsert([1,3,5,6], 5)); // 2
console.log(searchInsert([1,3,5,6], 2)); // 1
console.log(searchInsert([1,3,5,6], 7)); // 4
console.log(searchInsert([1], 0)); // 0
📝 JS 注意点:
- 使用
Math.floor()确保mid为整数;- JS 数组索引与 C++ 一致,逻辑完全相同。
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!