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

C++ 面试高频考点 力扣 35. 搜索插入位置 二分查找 左右端点查找 题解 每日一题

文章目录

  • 题目描述
  • 为什么这道题值得弄懂?
  • 为什么可以用二分?
  • 二分查找的两种实现思路(左端点定位 vs 右端点定位)
    • 思路一:左端点定位(找“最后一个≤target的元素”)
    • 思路二:右端点定位(找“第一个≥target的元素”)
  • 代码实现
    • 代码1:左端点定位(找“最后一个≤target的元素”)
    • 代码2:右端点定位(找“第一个≥target的元素”)
  • 总结:二分查找的关键细节

在这里插入图片描述

在这里插入图片描述

题目描述

题目链接:
力扣35. 搜索插入位置

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

示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4

注意:
时间复杂度为 O(log n)

为什么这道题值得弄懂?

再前面的三篇博客中我们都是通过题目来练习二分查找的,那么这道题目也不例外,我们依然通过这道题来练习二分,这道题目虽然是简单题思路好想,那么我们就可以通过在这道题的练习中梳理二分再写代码的时候会遇到的细节问题

为什么可以用二分?

首先我们可以思考为什么这道题可以用二分?
我们来看题目,首先这道题的已知是排序数组,并且给一个目标值,这是一个在有序数组中查找指定数的问题,我们就可以通过二段性来进行舍弃不符合条件的部分,并且“二分查找的核心是利用数组的‘二段性’—— 将数组分为‘满足条件’和‘不满足条件’的两部分,每次舍弃其中一部分,从而将时间复杂度压缩到 O (log n)

二分查找的两种实现思路(左端点定位 vs 右端点定位)

二分查找的关键是定义 “满足条件的区间”,并通过指针移动逐步缩小区间,最终定位到目标位置。本题中,目标位置有两种解读:

  • 左端点定位:找 “最后一个小于等于 target 的元素位置”,再根据该位置判断插入点;
  • 右端点定位:找 “第一个大于等于 target 的元素位置”,该位置直接就是插入点(若元素存在,就是元素索引)。

下面分别展开两种思路的细节分析。

思路一:左端点定位(找“最后一个≤target的元素”)

1. 二段性划分
将数组分为两部分:

  • 左半部分(满足条件):nums[i] ≤ target(目标值若存在,必在这部分;若不存在,插入点在这部分的最后一个元素之后);
  • 右半部分(不满足条件):nums[i] > target(这部分元素都比target大,可直接舍弃)。

示例:若nums = [1,3,5,6]target = 2,则满足条件的区间是[1](1≤2),不满足条件的区间是[3,5,6](3>2)。

2. 指针移动逻辑

  • right指针:若nums[middle] > target,说明middle及右侧都属于“不满足条件的右半部分”,直接舍弃,故right = middle - 1
  • left指针:若nums[middle] ≤ target,说明middle及左侧属于“满足条件的左半部分”,需保留middle(可能是最后一个满足条件的元素),故left = middle(而非middle + 1,避免漏掉目标)。

3. 循环结束条件
循环条件设为left < right,当left == right时,区间缩小到唯一元素,该元素就是“最后一个≤target的元素”,循环结束。

4. 中间值(middle)的取法
必须用向上取整middle = left + (right - left + 1) / 2(等价于(left + right + 1) // 2,避免整数溢出)。

为什么不用向下取整(left + (right - left)/2)?
若用向下取整,当区间长度为2时(如left=2, right=3,对应nums=[5,6]target=5):

  • 计算middle = 2 + (3-2)/2 = 2
  • nums[2] = 5 ≤ 5,执行left = middle = 2
  • 此时left仍等于2,right等于3,循环条件left < right仍成立,陷入死循环
  • 在这里插入图片描述

而向上取整会避免这种情况:<(^-^)>

  • 同样left=2, right=3middle = 2 + (3-2+1)/2 = 3
  • nums[3] = 6 > 5,执行right = 3-1 = 2
  • 此时left == right,循环结束,无死循环。

5. 最终插入点计算
循环结束后,left == right,指向“最后一个≤target的元素”,需分两种情况:

  • nums[left] == target:直接返回left(目标值存在);
  • nums[left] < target:插入点在left + 1(如target=7,最后一个≤7的元素是6,索引3,插入点4=3+1);
  • (补充:nums[left] > target的情况不存在!因为left是“最后一个≤target的元素”,不可能大于target,原文此处判断冗余,可删除)。

简化后返回逻辑return nums[left] == target ? left : left + 1

思路二:右端点定位(找“第一个≥target的元素”)

1. 二段性划分
将数组分为两部分:

  • 左半部分(不满足条件):nums[i] < target(这部分元素都比target小,可直接舍弃);
  • 右半部分(满足条件):nums[i] ≥ target(目标值若存在,必在这部分的第一个位置;若不存在,这部分的第一个位置就是插入点)。

示例:若nums = [1,3,5,6]target = 2,则不满足条件的区间是[1](1<2),满足条件的区间是[3,5,6](3≥2),第一个满足条件的元素是3,索引1,即插入点1(与示例2结果一致)。

2. 指针移动逻辑

  • left指针:若nums[middle] < target,说明middle及左侧都属于“不满足条件的左半部分”,直接舍弃,故left = middle + 1
  • right指针:若nums[middle] ≥ target,说明middle及右侧属于“满足条件的右半部分”,需保留middle(可能是第一个满足条件的元素),故right = middle(而非middle - 1,避免漏掉目标)。

3. 循环结束条件
与左端点定位一致,循环条件设为left < right,当left == right时,区间缩小到唯一元素,该元素就是“第一个≥target的元素”,循环结束。

4. 中间值(middle)的取法
必须用向下取整middle = left + (right - left) / 2(等价于(left + right) // 2)。

为什么不能用向上取整?
若用向上取整,当区间长度为2时(如left=0, right=1,对应nums=[1,3]target=2):

  • 计算middle = 0 + (1-0+1)/2 = 1
  • nums[1] = 3 ≥ 2,执行right = middle = 1
  • 此时left=0right=1,循环条件left < right仍成立,陷入死循环

而向下取整会避免这种情况:

  • 同样left=0, right=1middle = 0 + (1-0)/2 = 0
  • nums[0] = 1 < 2,执行left = 0 + 1 = 1
  • 此时left == right,循环结束,无死循环。

5. 最终插入点计算
循环结束后,left == right,指向“第一个≥target的元素”,该位置直接就是答案:

  • nums[left] == target:返回left(目标值存在);
  • nums[left] > target:返回left(目标值不存在,插入点就是第一个比target大的元素位置);
  • (特殊情况:若target比所有元素都大,如target=7nums=[1,3,5,6],则循环后left=4?不,初始right = nums.size()-1 = 3,循环中nums[middle] <7left逐步移到3,最终left=3nums[3]=6 <7?此处需纠正:当target比所有元素大时,“第一个≥target的元素”不存在于数组中,插入点是nums.size(),但循环后left会等于nums.size()-1,且nums[left] < target,故需补充判断:若nums[left] < target,返回left + 1)。

最终返回逻辑return nums[left] >= target ? left : left + 1(可简化为return left,因为当nums[left] < target时,left已是nums.size()-1left +1 = nums.size(),符合插入点要求)。

代码实现

代码1:左端点定位(找“最后一个≤target的元素”)

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;// 循环缩小区间,找最后一个<=target的元素while (left < right) {// 向上取整,避免死循环int middle = left + (right - left + 1) / 2;if (nums[middle] > target) {// 舍弃右半部分(middle及右侧)right = middle - 1;} else {// 保留左半部分(middle及左侧)left = middle;}}// 最终left == right,判断插入点return nums[left] == target ? left : left + 1;}
};

代码2:右端点定位(找“第一个≥target的元素”)

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;// 循环缩小区间,找第一个>=target的元素while (left < right) {// 向下取整,避免死循环int middle = left + (right - left) / 2;if (nums[middle] < target) {// 舍弃左半部分(middle及左侧)left = middle + 1;} else {// 保留右半部分(middle及右侧)right = middle;}}// 最终left == right,直接返回(若nums[left]<target,说明target比所有元素大,left+1 = nums.size())return nums[left] >= target ? left : left + 1;// 简化写法:return left; (原因见思路二的5点分析)}
};

总结:二分查找的关键细节

  1. 二段性是核心:先明确“满足条件的区间”是什么(如“≤target”或“≥target”),再确定舍弃哪部分;
  2. 中间值取整决定死循环
    • left = middle(保留左半部分),必须用向上取整(避免区间长度为2时死循环);
    • right = middle(保留右半部分),必须用向下取整(同理);
  3. 最终位置判断:左端点定位需判断“是否等于target”,右端点定位可直接返回(因“第一个≥target的位置”就是插入点)。

通过这道题,能彻底理清二分查找中“指针移动”和“中间值取整”的逻辑,后续遇到更复杂的二分问题(如找重复元素的左边界/右边界)也能举一反三。

http://www.dtcms.com/a/360879.html

相关文章:

  • RocksDB 在 macOS M 系列 上运行时报错的解决方案
  • 【公告】更新预告
  • vite基础讲解
  • 超越Transformer:语言模型未来的认知革命与架构重构
  • Golang之GoWorld深度解析:基于Go语言的分布式游戏服务器框架
  • 新启航技术白皮书:激光频率梳如何实现 130mm 深孔 2μm 级无遮挡 3D 轮廓测量
  • OpenCV-Python Tutorial : A Candy from Official Main Page(五)
  • 使用Spring Boot对接印度股票市场API开发实践
  • Burp Suite 插件 | 提供强大的框架自动化安全扫描功能。目前支持1000+POC、支持动态加载POC、指定框架扫描。
  • 一体化运维平台自动化模块:3 大场景解放运维双手
  • 开发中使用——鸿蒙CoreSpeechKit语音识别
  • 复杂计算任务的智能轮询优化实战
  • 教育项目管理工具新趋势:可视化与自动化如何提升效率?
  • 使用ansible的playbook完成以下操作
  • TFS-2010《Fuzzy PCA-Guided Robust k-Means Clustering》
  • macOS中Homebrew安装PHP的详细步骤(五)
  • React学习教程,从入门到精通, React 入门指南:创建 React 应用程序的语法知识点(7)
  • 反物质量子比特初探
  • [免费]基于Python的气象天气预报数据可视化分析系统(Flask+echarts+爬虫) 【论文+源码+SQL脚本】
  • 【iOS】关键字复习
  • 【iOS】折叠cell
  • 量子電腦組裝
  • FunASR开源部署中文实时语音听写服务(CPU)
  • 配送算法19 Two Fast Heuristics for Online Order Dispatching
  • windows10专业版系统安装本地化mysql服务端
  • 【Docker】Docker的容器Container、镜像Image和卷Volume对比
  • Centos安装unoconv文档转换工具并在PHP中使用phpword替换word模板中的变量后,使用unoconv将word转换成pdf
  • openharmony之sandbox沙箱机制详解
  • Docker一键快速部署压测工具,高效测试 API 接口性能
  • datax将数据从starrocks迁移至starrocks