当前位置: 首页 > news >正文

Leetcode二分查找(4)

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。

解题思路总览

  1. 标准二分(lower_bound 半开区间写法)
  2. 标准二分(闭区间写法)
  3. 递归二分
  4. 利用 Java Arrays.binarySearch 封装处理
  5. 变体:位运算取中 / 防溢出与模板细节
  6. 拓展:未知长度数组的指数扩展 + 二分(非本题必需,思路扩展)

题目回顾:给定升序(非递减)整数数组 nums 与 target,若存在返回其索引,否则返回按顺序插入后仍保持有序的索引(即第一个 >= target 的位置)。要求 O(log n)。这就是典型的 lower_bound 问题。


思路一:标准二分(lower_bound 半开区间写法)

原理与适用场景:
采用半开区间 [l, r) 表示当前搜索范围,维持循环不变量:目标插入位置一定在 [l, r) 中。中点 mid = l + (r - l)/2。若 nums[mid] >= target,将可能的插入位置右边界收缩到 mid;否则抛弃左半包含 mid 的部分,令 l = mid + 1。最终 l == r 即为第一个 >= target 的位置:

  1. 若该位置在数组长度内且值等于 target,则它就是目标索引。
  2. 否则该位置就是插入点。
    半开区间避免处理 mid - 1 / mid + 1 时越界边界判断,模板简洁统一,可复用于各类 lower_bound / upper_bound 问题。

实现步骤:

  1. 令 l = 0, r = n(注意 r 为 n,而不是 n-1)。
  2. while(l < r):计算 mid。
  3. 若 nums[mid] >= target:r = mid;否则 l = mid + 1。
  4. 循环结束返回 l。

JAVA 代码实现:

public class SolutionLowerBound {// 返回 target 的索引或其应插入位置(第一个 >= target 的下标)public int searchInsert(int[] nums, int target) {int n = nums.length;              // 数组长度int l = 0;                        // 左边界(包含)int r = n;                        // 右边界(不包含)while (l < r) {                   // 区间非空int mid = l + (r - l) / 2;    // 取中,防溢出if (nums[mid] >= target) {    // mid 位置可能是第一个 >= targetr = mid;                  // 收缩右端到 mid} else {                      // nums[mid] < targetl = mid + 1;              // 插入点一定在 mid 右侧}}return l; // l == r 为第一个 >= target 的位置(可能等于 n)}
}

思路二:标准二分(闭区间写法)

原理与适用场景:
使用闭区间 [l, r]。循环条件 l <= r。维护答案变量 ans,记录第一个 >= target 的候选位置。每当 nums[mid] >= target,用 ans = mid 保存并 r = mid - 1 尝试更左;否则 l = mid + 1。最终若 ans 未被更新(保持初值 n)则插入位置在数组末尾。闭区间写法适合对传统二分 while(l <= r) 更熟悉的人。

实现步骤:

  1. ans = n(默认插入到末尾)。
  2. while(l <= r) 取 mid。
  3. 若 nums[mid] >= target:更新 ans,r = mid - 1。
  4. 否则 l = mid + 1。
  5. 返回 ans。

JAVA 代码实现:

public class SolutionClosedInterval {public int searchInsert(int[] nums, int target) {int n = nums.length;          // 长度int l = 0;                    // 左边界int r = n - 1;                // 右边界int ans = n;                  // 默认插入到末尾while (l <= r) {              // 闭区间还存在元素int mid = l + (r - l) / 2;// 中点if (nums[mid] >= target) {// mid 可能是答案ans = mid;            // 记录候选r = mid - 1;          // 尝试找更左} else {                  // nums[mid] < targetl = mid + 1;          // 去右区间}}return ans;                   // 第一个 >= target 的位置}
}

思路三:递归二分

原理与适用场景:
递归在区间 [l, r] 中寻找第一个 >= target 的位置。若 l > r 返回 n(表示未找到更小位置)。取 mid:

  1. 若 nums[mid] >= target:答案在左半或 mid,自身与左半递归返回值取最小。
  2. 否则在右半。
    递归深度 O(log n)。适合希望把二分统一为递归模板的场景。

实现步骤:

  1. 递归函数返回第一个 >= target 的索引或 n。
  2. 初始调用 dfs(nums, 0, n-1, target, n)。
  3. 返回结果。

JAVA 代码实现:

public class SolutionRecursiveLB {public int searchInsert(int[] nums, int target) {return dfs(nums, 0, nums.length - 1, target, nums.length);}private int dfs(int[] a, int l, int r, int target, int n) {if (l > r) {              // 区间空,返回 n 代表插入末尾return n;}int mid = l + (r - l) / 2;// 取中if (a[mid] >= target) {   // mid 可能为答案,继续探索左侧int left = dfs(a, l, mid - 1, target, n);return Math.min(mid, left); // 取更左的} else {                  // a[mid] < target,只能右侧return dfs(a, mid + 1, r, target, n);}}
}

思路四:利用 Java Arrays.binarySearch 封装处理

原理与适用场景:
Java 标准库 Arrays.binarySearch(nums, target) 返回:

  1. 若找到:返回 >=0 的索引。
  2. 若未找到:返回 (-(插入点) - 1)。插入点即第一个 > target 的下标,也即第一个 >= target(因为未找到等于)。
    因此直接解码即可得到答案。适合快速实现,降低样板代码量,但在某些面试中展示手写二分更能体现功底。

实现步骤:

  1. 调用 idx = Arrays.binarySearch(nums, target)。
  2. 若 idx >= 0 返回 idx。
  3. 否则插入点 = -idx - 1。
  4. 返回插入点。

JAVA 代码实现:

import java.util.Arrays; // 导入工具类public class SolutionLibrary {public int searchInsert(int[] nums, int target) {int idx = Arrays.binarySearch(nums, target); // 标准库二分if (idx >= 0) {          // 找到直接返回return idx;}// 未找到,idx = -(insertionPoint) - 1return -idx - 1;         // 解码得到插入点}
}

思路五:位运算取中 / 模板细节(属于写法优化,不单独改变复杂度)

原理与适用场景:
在极端性能场景可用 mid = (l + r) >>> 1(无符号右移)替代 l + (r - l)/2;在 Java 中 int 溢出时 l + r 可能为负导致结果错误,因此推荐使用 l + (r - l)/2 或 (l + r) >>> 1(当 l,r >=0 时安全)。属于实现细节优化,可结合思路一或二使用。

实现步骤:

  1. 在二分循环中用 mid = (l + r) >>> 1。
  2. 其余逻辑不变。

(示例略,因核心逻辑同思路一。)


思路六:拓展 - 未知长度数组 / 流式数据(指数扩展 + 二分)

原理与适用场景:
若无法直接获得数组长度(例如某些 API 仅支持 get(i) 且越界抛异常),可先指数扩展边界:

  1. 令 r = 1,不断检查 nums[r] 与 target 比较,若 nums[r] < target 则 r *= 2。
  2. 找到第一个 nums[r] >= target 或越界时停止,此时目标插入点一定在 (r/2, r] 内。
  3. 在这个区间执行常规二分。指数扩展步数 O(log pos),整体仍 O(log n)。
    本题已给定长度,不必使用,只作思维拓展。

实现步骤(高层):

  1. 边界扩展。
  2. 二分搜索 lower_bound。
  3. 返回结果。

补充说明(对比分析)

  1. 正确满足 O(log n) 的实现:思路一、二、三、四、六(六为扩展情景)。
  2. 复杂度:
    • 思路一/二/三/四:时间 O(log n),空间 O(1)(递归 O(log n))。
    • 思路六:时间 O(log n),空间 O(1)(若递归则 O(log n))。
  3. 推荐优先级:
    • 首选思路一(半开区间 lower_bound 模板)。
    • 需要展示传统写法可选思路二。
    • 递归视个人风格;库函数写法快速但展示度较低。
  4. 边界与健壮性:
    • 空数组:半开区间写法自动返回 0。
    • target 小于最小值 -> 返回 0。
    • target 大于最大值 -> 返回 n。
    • 全部元素相同:lower_bound 仍正确给出 0 或 n(若 target 更大)。
  5. 常见错误:
    • 闭区间写法退出条件混乱导致死循环(如用 while(l < r) 但更新 r = mid - 1)。
    • mid = (l + r)/2 可能溢出(极大数据时)。
    • 返回 r 或 l 搞错:需基于循环不变量严格推导。
  6. 测试用例建议:
    • nums=[1,3,5,6], target=5 -> 2
    • nums=[1,3,5,6], target=2 -> 1
    • nums=[1,3,5,6], target=7 -> 4
    • nums=[1,3,5,6], target=0 -> 0
    • nums=[], target=5 -> 0
    • nums=[1], target=1 -> 0
    • nums=[1], target=2 -> 1
    • nums=[1,1,1], target=1 -> 0
  7. 总结:本题本质是 lower_bound,掌握一套稳健模板(思路一)即可在同类区间定位、统计频次、插入位置等问题上快速迁移。

综上,采用半开区间 lower_bound 模板实现最简洁、低错率、可直接推广,是最推荐解法。


文章转载自:

http://U3CgSld3.sjwws.cn
http://9hcS0RUO.sjwws.cn
http://3JHonTuf.sjwws.cn
http://10clKw7D.sjwws.cn
http://BWrH3Pdp.sjwws.cn
http://shXni9TH.sjwws.cn
http://MDRgH2xm.sjwws.cn
http://dk211vRB.sjwws.cn
http://zsDUuy60.sjwws.cn
http://ZiWFQQSb.sjwws.cn
http://wIYKpUwp.sjwws.cn
http://VcZlR4Yj.sjwws.cn
http://Si9DrDxF.sjwws.cn
http://ZfMdi0a2.sjwws.cn
http://IVy5fvQR.sjwws.cn
http://oSFzMaMb.sjwws.cn
http://XMFxXCvn.sjwws.cn
http://YhZdMeCr.sjwws.cn
http://AWNPEm0m.sjwws.cn
http://rtdvU0R2.sjwws.cn
http://62OonKfw.sjwws.cn
http://6grs4MkJ.sjwws.cn
http://PuWtswnh.sjwws.cn
http://TeDrpd6t.sjwws.cn
http://ZwfIXVbK.sjwws.cn
http://VoXdwpfw.sjwws.cn
http://1OHIA1D8.sjwws.cn
http://097acWEt.sjwws.cn
http://rGV25Gjd.sjwws.cn
http://ALwNYB6f.sjwws.cn
http://www.dtcms.com/a/363322.html

相关文章:

  • 开悟篇Docker从零到实战一篇文章搞定
  • 洗衣店小程序的设计与实现
  • GDB 调试
  • 深度学习篇---DenseNet网络结构
  • Spring Boot手写10万敏感词检查程序
  • C#----异步编程
  • 基于Django的论坛系统设计与实现(代码+数据库+LW)
  • Qt模型/视图编程详解:QStringListModel与多视图数据同步
  • 链表题类型注解解惑:理解Optional,理解ListNode
  • 前端实现解析【导入】数据后调用批量处理接口
  • GaussDB 等待事件为LockMgrLock处理方法
  • 为什么程序员总是发现不了自己的Bug?
  • flutter踩坑插件:Swift架构不兼容
  • 疯狂星期四文案网第58天运营日记
  • 手撕Redis底层2-网络模型深度剖析
  • 【3D 入门-4】trimesh 极速上手之 3D Mesh 数据结构解析(Vertices / Faces)
  • Valkey vs Redis详解
  • 基于若依框架开发WebSocket接口
  • 计算机Python毕业设计推荐:基于Django+Vue用户评论挖掘旅游系统
  • 【交易系统系列36】揭秘币安(Binance)技术心脏:从公开信息拼凑“MatchBox”撮合引擎架构
  • 海康摄像头开发---标准配置结构体(NET_DVR_STD_CONFIG)
  • End-To-End 之于推荐-kuaishou OneRec2 笔记
  • css中 ,有哪些⽅式可以隐藏页⾯元素? 区别?
  • 03_网关ip和端口映射(路由器转发)操作和原理
  • Telnet 原理与配置
  • 基于STM32单片机智能家居wifi远程监控系统机智云app设计
  • Replit在线编程工具:支持多语言环境免配置与实时协作,助力编程学习调试与社区项目复用
  • Spring Security的@PreAuthorize注解为什么会知道用户角色?
  • 0902 C++类的匿名对象
  • Nano Banana 复刻分镜,多图结合片刻生成想要的视频