【LeetCode Hot100 刷题日记 (63/100)】35. 搜索插入位置 —— 二分查找(Binary Search)🧠

4 阅读5分钟

📌 题目链接: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 在大数时溢出(虽然本题数据范围不会,但这是工程好习惯!)


🧩 解题思路(分步拆解)

  1. 明确问题本质
    不是简单“找是否存在”,而是“找第一个 ≥ target 的位置”——这是二分查找的典型应用场景。

  2. 确定搜索区间与终止条件
    使用 [left, right] 闭区间,当 left > right 时结束,此时 ans 已记录最优解。

  3. 处理边界情况

    • target 小于所有元素 → 返回 0;
    • target 大于所有元素 → 返回 n
    • 通过初始化 ans = n 自动覆盖后者,前者由二分过程自然得出。
  4. 更新策略

    • nums[mid] >= target:当前位置可能是答案,但左边可能还有更优解 → 记录 ans = mid,并 right = mid - 1
    • 否则:left = mid + 1
  5. 返回结果
    循环结束后,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!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!