Hot 100 --- 搜索插入位置

本文概览:本文讲解搜索插入位置的核心思路:在有序数组中查找 target,找到返回索引,找不到返回应该插入的位置


一、题目


二、题目分析

这题的要求是:给定一个有序数组 和一个目标值 target,在数组中找到目标值并返回其索引。如果目标值不存在,则返回它应该被按顺序插入的位置。

比如数组 [1, 3, 5, 7]

  • 查找 target = 3,返回索引 1
  • 查找 target = 4,返回插入位置 2(插入后数组为 [1, 3, 4, 5, 7]

这题可以用二分查找实现 O(log n) 的时间复杂度,前提是数组一定有序。

二分查找的思路就是:每次查询都查询目前查找范围的中间点,判断中间点的值大于还是小于 target。


思路概览

Java 实现代码如下

java 复制代码
public int searchInsert(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] == target) {
            return mid;
        } else if(nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return left;
}

思路简要说明:

  1. 初始范围left = 0right = nums.length - 1
  2. 中间点计算mid = left + (right - left) / 2,避免溢出
  3. 范围更新 :小于 target 时 left = mid + 1,大于时 right = mid - 1
  4. 终止条件left > right 说明搜索完毕,返回 left 就是插入位置

三、思路详解

第一步:为什么需要有序数组

二分查找的核心是"每次排除一半的搜索范围"。如果数组无序,中间点的值无法告诉我们 target 在左边还是右边,也就无法排除一半。

比如升序数组 [1, 3, 5, 7, 9],查找 target = 7:

  • 中间点是 5,5 < 7,说明 7 一定在右边(因为数组升序,左边的值都小于 5)
  • 排除左半部分,搜索范围变为 [7, 9]
  • 中间点是 7,找到目标
第二步:中间点的计算

中间点的计算公式是:

java 复制代码
int mid = left + (right - left) / 2;

为什么不直接写 (left + right) / 2

因为如果 leftright 都很大,left + right 可能溢出 int 的取值范围 。比如 left = 2^30right = 2^30,相加就超过了 Integer.MAX_VALUE

left + (right - left) / 2 是先算距离再除以 2,不会溢出。

第三步:范围更新

假设数组升序,每次查询中间点后:

  • numsmid == target:找到目标,直接返回
  • numsmid < target :target 一定在右边,更新 left = mid + 1
  • numsmid > target :target 一定在左边,更新 right = mid - 1

为什么是 mid + 1mid - 1,不是 mid

因为 mid 已经检查过了,不是目标值,所以下一次搜索范围不包含 mid

第四步:终止条件和插入位置

循环条件是 left <= right,当 left > right 时退出循环。

这说明搜索范围为空,没有找到 target。

关键问题:为什么返回 left 就是插入位置?

当循环结束时,left = right + 1,此时有两种情况:

  1. 最后一步是 left = mid + 1 :说明 nums[mid] < targetmid 左边的值都小于 target,所以 target 应该插入到 mid + 1 的位置,也就是 left
  2. 最后一步是 right = mid - 1 :说明 nums[mid] > targetmid 右边的值都大于 target,所以 target 应该插入到 mid 的位置,而此时 left = mid(因为 right = mid - 1,循环结束意味着 left = right + 1 = mid

所以无论哪种情况,返回 left 就是 target 应该插入的位置。

第五步:完整执行过程

例子 1:找到 target

nums = [1, 3, 5, 7, 9]target = 7 为例:

复制代码
初始:left=0, right=4

第 1 轮:
  mid = 0 + (4-0)/2 = 2
  nums[2] = 5 < 7
  left = mid + 1 = 3

第 2 轮:
  left=3, right=4
  mid = 3 + (4-3)/2 = 3
  nums[3] = 7 == 7
  返回 3 ✓

例子 2:找不到 target,返回插入位置

nums = [1, 3, 5, 7, 9]target = 6 为例:

复制代码
初始:left=0, right=4

第 1 轮:
  mid = 2, nums[2] = 5 < 6
  left = 3

第 2 轮:
  left=3, right=4
  mid = 3, nums[3] = 7 > 6
  right = 2

第 3 轮:
  left=3, right=2
  left > right,退出循环
  返回 left = 3

6 应该插入到索引 3 的位置,插入后数组为 [1, 3, 5, 6, 7, 9]

例子 3:插入到末尾

nums = [1, 3, 5]target = 7 为例:

复制代码
初始:left=0, right=2

第 1 轮:
  mid = 1, nums[1] = 3 < 7
  left = 2

第 2 轮:
  left=2, right=2
  mid = 2, nums[2] = 5 < 7
  left = 3

第 3 轮:
  left=3, right=2
  left > right,退出循环
  返回 left = 3(插入到末尾)

例子 4:插入到开头

nums = [3, 5, 7]target = 1 为例:

复制代码
初始:left=0, right=2

第 1 轮:
  mid = 1, nums[1] = 5 > 1
  right = 0

第 2 轮:
  left=0, right=0
  mid = 0, nums[0] = 3 > 1
  right = -1

第 3 轮:
  left=0, right=-1
  left > right,退出循环
  返回 left = 0(插入到开头)
第六步:时间复杂度分析

每次搜索范围减半,所以时间复杂度是 O(log n)。

比如数组长度 1024,最多查 10 次(2^10 = 1024),而线性查找最多要查 1024 次。


四、总结

搜索插入位置的核心是三个要素:

  1. 有序数组:这是前提
  2. 中间点计算left + (right - left) / 2 避免溢出
  3. 范围更新:根据中间点和 target 的大小关系,排除一半的搜索范围
  4. 插入位置 :循环结束时返回 left,就是 target 应该插入的位置

这个思路可以推广到所有"有序数组查找和插入"类题目。

相关推荐
Jul1en_1 小时前
【Claude Code Compact】源码级别的学习上下文压缩
java·前端·学习·github·ai编程
alexwang2111 小时前
HDU 4348 详细题解
c++·算法·题解·hdu·主席树·可持久化数据结构·可持久化线段树
前端 贾公子1 小时前
第06章:结构化输出 (上)
java·服务器·前端
念何架构之路2 小时前
restartmanager-重启管理子系统
java·开发语言
Java内核笔记2 小时前
告别第三方库!Spring Boot 4 原生 API 版本控制全解析:4 种策略 + 实战案例
java·后端
可编程芯片开发2 小时前
基于分段变步长搜索RMDCFT算法的天基雷达空间机动目标检测方法MATLAB仿真,对比RMDCFT算法
算法
小小洋洋2 小时前
OpenWrt 从U盘迁移到内置 eMMC,并完成扩容与 Docker 安装
java·docker·eureka
2501_946736162 小时前
在线考试平台如何选型?从功能、优势与应用场景角度详解
大数据·人工智能·算法
就改了3 小时前
SpringBoot 自定义线程池 + 实时监控指标
java·spring boot·后端