力扣刷题Day1:704二分查找与35搜索插入位置详解

发布时间:2026/10/11 4:57:42

力扣刷题Day1:704二分查找与35搜索插入位置详解 1. 为什么第一天应该先从704和35这对组合下手如果你开始刷力扣随便问一个过来人“入门第一题选什么”大概率得到的答案是704。这个题号对应的就是二分查找而35则是它的“亲兄弟”——搜索插入位置。把这俩放在Day1不是巧合是我个人实测下来最高效的冷启动方式。先说一个反直觉的事实二分查找这道题看着代码就十来行但面试时手写通过率并不高。真正卡人的不是“有没有听说过二分”而是对区间定义、循环条件、边界更新这三件事的拿捏。704恰好把“标准二分”讲透了35又在这基础上加了一层“插入位置”的变体两道题连刷刚好把二分最核心的思维模型焊死在脑子里。从刷题节奏上看Day1不宜碰太复杂的综合题。链表、二叉树、动态规划都不适合作为起点因为前置知识太多挫败感太强。二分查找的数学基础只是“数组有序 查找目标”任何有一点编程基础的人都能在半小时内理解原理、一小时左右跑通代码。我见过很多转码的朋友第一题就选了个Hard结果三天没做出来直接放弃。第一天选704实质是给你建立一个“我也能刷题”的正反馈循环。这篇内容适合谁准备笔试面试的应届生、想系统补算法的在职开发、以及刚开始刷 LeetCode 、还摸不着头脑的纯新手。我会把两道题的完整思考链、代码写法、边界坑一次性讲明白并且给我自己在实际调试中踩过的具体问题。你会看到的不只是答案而是“我在做这道题时到底在想什么”。2. 二分查找的运行逻辑模板背后的边界秘密左闭右闭与左闭右开很多教程一上来就给你二分模板然后说“背下来就行”。我不太赞同这种方式。二分这东西背模板的人一旦换题就懵因为面试官稍微改一下搜索条件比如找最左边界、最右边界你的模板就失灵了。让我先把运行逻辑拆透。2.1 为什么二分能用有序数组的搜索减半原理二分查找的思想概括成一句话通过比较中间元素与目标值每次把搜索范围缩小一半。时间复杂度从顺序查找的 O(n) 降到 O(log n)在数据量大的时候完全是质变。这个原理生活里到处都是——比如你查一本字典不会从第一页翻到最后一页而是先翻到中间看目标词在左边还是右边然后继续折半。但算法题里有一个关键前提数组必须是有序的。704题明确给了“升序排列的数组”所以二分可以成立。如果数组无序你用二分得到的结果就是错的。这是很多人实现时忽略的先验条件。2.2 区间定义是所有后续动作的“宪法”写二分代码你要先回答一个问题我维护的搜索区间是左闭右闭 [left, right] 还是左闭右开 [left, right)这两种定义都常见没有绝对的对错但你必须从头到尾贯彻同一种定义。我最开始就是吃了这个亏——循环里用了左闭右闭的写法更新边界时却按左闭右开的逻辑写最终导致死循环或漏答案。如果采用左闭右闭即 left 0, right len(nums) - 1那么 left 和 right 指向的元素都可能在搜索范围内。这时候循环条件必须是while left right因为当 left right 时当前这个位置还没有被检查过当 nums[mid] target右边范围应更新为 mid - 1因为 nums[mid] 已经确定不是目标了没必要再放进下一轮搜索当 nums[mid] target左边范围应更新为 mid 1。如果采用左闭右开即 left 0, right len(nums)那么 right 指向的元素一定不在搜索范围内。这时候循环条件是while left right因为当 left right 时搜索区间已经空了当 nums[mid] targetright mid因为 nums[mid] 已经在范围外不需要减一当 nums[mid] targetleft mid 1。这两种写法我建议你只选一种反复练。我自己更常用左闭右闭因为它在返回结果时更直观——找到目标就返回下标没找到时 left 的位置也更好解释下一题35会用到这个性质。2.3 为什么很多人的二分会死循环死循环几乎是二分新手最常撞的坑。根源就在于区间定义与循环条件不匹配。举个例子如果你用了左闭右开while left right却在 nums[mid] target 时更新 right mid - 1会怎样假设 left 0, right 1, mid 0nums[0] 大于目标你执行 right -1搜索区间直接变成负数区间逻辑就乱了。反过来左闭右闭时更新 right mid则可能出现 left 和 right 永远相等或交错的情况导致循环无法退出。我的经验是每写完一行更新逻辑就问自己“这个位置的值到底还可能在搜索范围内吗”如果在就不要越界排除如果不在就果断排除。这套判断比死记模板可靠得多。3. 704题完整拆解从读题到AC的每一步这一节我们直接实战。704的题目描述非常干净给定一个升序数组 nums 和一个目标值 target找到 target 在数组中的下标如果不存在则返回 -1。示例输入nums [-1,0,3,5,9,12], target 9输出4。输入nums [-1,0,3,5,9,12], target 2输出-1。3.1 标准的左闭右闭解法我先把完整代码给出然后用注释拆解每一步为什么这样写def search(self, nums: List[int], target: int) - int: left, right 0, len(nums) - 1 # 左闭右闭区间 while left right: # 左闭右闭必须用 mid left (right - left) // 2 # 防止溢出写法 if nums[mid] target: return mid # 命中目标直接返回下标 elif nums[mid] target: left mid 1 # 目标在右半区收缩左边界 else: right mid - 1 # 目标在左半区收缩右边界 return -1 # 循环结束说明没找到这里面我特别讲一下 mid 的计算。常规写法是(left right) // 2但 left right 在极端情况下可能超过整数上限虽然 Python 的 int 没有溢出问题但在 C / Java 里这是经典隐患。我建议所有语言一律写成left (right - left) // 2从根源上规避这个问题。这一点面试时会成为加分项。3.2 左闭右开写法对照为了让你彻底理解两种区间的差异我也贴一下左闭右开的版本def search(self, nums: List[int], target: int) - int: left, right 0, len(nums) # 左闭右开区间 while left right: # 左闭右开必须用 mid left (right - left) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 # 目标在右半区 else: right mid # 注意不是 mid - 1 return -1对比两段代码唯一的差别就在三处right 的初始值、循环条件、右边界更新。我把这个对照表放在这里方便你记忆对比项左闭右闭 [left, right]左闭右开 [left, right)right 初始值len(nums) - 1len(nums)循环条件left rightleft right右边界更新right mid - 1right mid左边界更新left mid 1left mid 1我自己调试的时候如果代码跑出死循环第一件事就是检查这三项是否匹配。几乎所有的二分 Bug 都出在这个三角关系上。3.3 调试时我怎么看输出这里分享一个我的实操技巧在 while 循环里打印 left、right、mid 三个变量的演化过程。比如 target 2nums [-1,0,3,5,9,12] 这种不存在的值你打印出来会看到 left 不断逼近 right 然后交错退出整个“收缩过程”一目了然。我刚开始学的时候每次二分跑不出预期结果就加 print 看状态。虽然力扣提交时不要求但这个习惯帮你建立了对“区间收缩”的直觉。等熟练之后你就不需要打印了因为看到代码就能脑内模拟整个搜索过程。4. 35题的“插入位置”变体二分结束后 left 指向哪里704解决的是“查找目标”而35在此基础上问了一个更微妙的问题如果目标不存在返回它应该被插入的位置使得数组依然有序。比如 nums [1,3,5,6], target 5返回 2target 2返回 1target 7返回 4target 0返回 0。4.1 为什么这题是“704的天然续集”表面看35就是加了一个“没找到时返回插入点”的要求实现上却逼迫你理解一个重要性质当二分查找结束时left 停下的位置就是目标值理应在的位置。这一点怎么理解回到左闭右闭的流程中left 的移动逻辑是left mid 1也就是说当目标值大于中间值时左侧已经不可能是插入位于是把范围右移。循环结束后left 指向的位置左侧的所有元素都小于 target右侧所有元素都大于或等于 target——这个位置恰好满足“插入后序列依然有序”的定义。很多人会想着用额外变量记录 last_mid 之类的值其实完全没必要。直接复用704的框架return left 即可。4.2 完整解法与关键注释def searchInsert(self, nums: List[int], target: int) - int: left, right 0, len(nums) - 1 while left right: mid left (right - left) // 2 if nums[mid] target: return mid # 找到目标直接返回下标 elif nums[mid] target: left mid 1 # 插入位置一定在右侧 else: right mid - 1 # 插入位置一定在左侧 return left # 循环结束left 就是插入位置注意最后一行 return left 而不是 return right 或 return mid。我实测过很多人在这里会犹豫。用左闭右闭时循环结束后 left 会停在第一个大于 target 的位置或者数组末尾而 right 会停在 left 左侧。如果要维持有序插入必须是 left。4.3 边界情况实测全小于、全大于、命中左端为了验证这套逻辑的鲁棒性我带你把边界情况过一遍target 小于所有元素比如 nums [1,3,5,6], target 0。第一轮 mid 指向 3nums[3] 5 0right 缩到 1第二轮 mid 指向 1nums[1] 3 0right 缩到 0第三轮 mid 指向 0nums[0] 1 0right 缩到 -1。循环结束left 0返回 0正确。target 大于所有元素比如 target 7。循环过程中 left 持续右移最终 left 4返回 4正好是数组末尾之后的位置正确。target 命中数组左端比如 target 1。第一轮 mid 指向下标 2 的 5大于 1right 缩到 1第二轮 mid 指向下标 1 的 3大于 1right 缩到 0第三轮 mid 指向下标 0 的 1命中返回 0。一次耦合都没有偏差。这三组用例跑通之后你基本可以放心提交了。5. 写二分法最容易翻车的三类错误与调试思路这一节从实际调试经验出发聊聊我在带别人刷题和自己练题时最常见的三类翻车情况以及对应的排查链路。5.1 死循环怎么判断是区间定义不一致死循环的典型表现是编译能过运行超时。力扣会在超时后停掉程序并提供最后执行的用例。我看到超时的第一反应不是怀疑算法思路而是怀疑循环条件与边界更新不匹配。排查路径是拿出一张纸把 left、right、mid 的初始值写下来然后手动模拟第二轮、第三轮。如果发现某一轮更新后 left 和 right 不再靠近而是原地打转那基本就是更新条件写错了。最常见的死循环场景是使用左闭右开区间时误把right mid - 1写进去。当 left 0, right 1 时mid 0如果目标是往左区间找应该 right 0但写成 right -1 就跳出了合法区间。反过来左闭右闭时误写right mid可能让 left 和 right 始终差 1永远不退出。死循环的本质不是“循环太多次”而是区间更新没有产生单调收敛。5.2 边界下标错误为什么返回 left 而不是 right35题里这个坑太典型了。不少人的代码返回的是 right跑示例用例时碰巧对了但提交后边缘用例挂掉。原因在于左闭右闭的循环结束后left 和 right 的关系是错位的——left 指向第一个不小于目标值的位置right 指向最后一个小于目标值的位置。也就是说它们俩“交错”了i.e., right 1 left。这种情况下返回 left 才是插入位right 是目标的前一个位置。比如 nums [1,3], target 2循环结束后 left 1, right 0返回 left 是 1插在 1 和 3 之间返回 right 就成了 0显然错误。如果你记不清楚就记住一句话二分没找到时left 永远指向“目标应该在的位置”。5.3 整数溢出与负数下标mid 的计算方式我之前提过这里再展开说一下。如果 mid 算出来是一个负数或者异常大的数通常不是溢出而是数组本身为空或 right 初始化出错。当 nums 为空时len(nums) - 1 -1此时 while left right 不成立函数直接返回 -1 或 035题返回 0。这个行为对704是合理的空数组里不可能有目标对35也是合理的空数组理应插在第一位。但有一个隐蔽问题如果你把 right 初始化为len(nums)但循环条件却写成了while left right那么 mid 可能取到len(nums)访问数组就越界了。这种错误在 Python 里表现为 index out of range在 C 里是未定义行为。我的建议是先用空数组、单元素数组做一遍用例测试再提交。这两个用例基本能暴露九成以上的边界问题。5.4 一份自查清单下面这张表是我刷二分类题目时的个人自查清单每次提交前过一遍检查项目标状态区间定义明确左闭右闭还是左闭右开第一步就定死循环条件匹配 配左闭右闭 配左闭右开右边界更新不把仍在搜索范围的元素排除mid 计算left (right - left) // 2返回位置找到返回 mid没找到看题意决定返回 left 还是 -1空数组用例提交前先测 nums[]单元素用例提交前先测 nums[x]target 分别大于、小于、等于 x这套清单帮我稳定解决了不少二分变种题比如“查找第一个等于 target 的索引”这类稍微复杂的问题本质也是基于这套框架做细微改动。6. 从Day1延伸下去二分家族的题单与学习心法704和35只是二分这个大家族的第一站。当你把这两题吃透后后面其实还有一堆变种等着你但它们的内核都是同一个有序空间上的折半搜索。6.1 二分题目的进阶脉络我按照自己刷题的经验给二分系列排一个由易到难的参考顺序704 二分查找标准模板先掌握区间定义。35 搜索插入位置理解 left 的语义学会处理“不存在”的情况。34 在排序数组中查找元素的第一个和最后一个位置把二分扩展到查找左右边界需要对模板做两套微调。洛谷和力扣都有类似题这是面试高频。69 x 的平方根数值二分的一个经典应用目标不是数组下标而是一个整数答案利用二分把试错过程从 O(n) 降到 O(log n)。153 寻找旋转排序数组中的最小值把二分的“有序性”前提放宽到“部分有序”很多人第一次接触会觉得棘手但看完题解会发现还是不变应万变。33 搜索旋转排序数组进一步在旋转数组中查找目标值思路是先判断哪半边有序再决定搜索方向。我的个人体会是不要急着一天刷完这一串。第一天把704和35练到“闭着眼能写对”的程度第二天再看34和69效果远好于一次性堆砌。学习算法有一个被低估的点让知识和思维在大脑里“过夜沉淀”第二天你会惊讶地发现头一天还在挠头的写法今天已经能秒写了。6.2 我踩过的两个低效刷题误区这里说两个我早期刷题时踩过的误区希望你能避开。第一个误区是**“打开力扣就开始想想不出来就看题解”**。这种模式下大脑没有自主形成思考链路下次遇到还是不会。我的做法是给自己定一个“30分钟原则”——如果30分钟内完全没有推进或者方向明显错了才去看题解。看题解不是目的关键是看完之后合上代码自己重新写一遍并且把这道题的“题眼”总结成一句话写在笔记里。704的题眼是“区间定义与循环条件匹配”35的题眼是“left即插入位”就这么一句话比抄十遍代码都有用。第二个误区是**“只做新题不做旧题”**。我刷题有一个习惯每天开始新题之前把前一天做过的题用白纸默写一遍。这个习惯看似浪费时间实际上是把短期记忆固化为长期能力。704和35这种基础题我至少重复写了两周直到完全不需要思考就能写对为止。基础题的肌肉记忆决定了你遇上综合题时的天花板。6.3 Day1 之后如何保持刷题节奏如果你已经刷到这一步恭喜最难的其实是“开始”。Day2、Day3 的题目难度评估往往比 Day1 更复杂你会发现前一题明明做对了后一题换个场景又不会。这不是你退步了而是算法能力本来就是这种“螺旋上升”的过程同一种思路在不同外壳下反复出现每一次你都会理解得更深一点。我个人比较推荐的节奏是每周定一个算法主题比如第一周二分、第二周双指针、第三周哈希表。每天至少一题周末把本周的题目重新过一遍。重点不是数量而是你能否给每个做题思路写下“什么时候用得上”的解释。704和35就是二分主题里最完美的开场白它们能帮你建立一套模式识别的起点后续所有二分变种都可以从这套模式中生长出去。最后分享一个纯粹的个人经验刷题这件事最快乐的不是 AC 的那个瞬间而是某一天你突然发现以前需要看题解才懂的二分现在自己拿到题目几分钟就能优雅地写出来。这个转变是从 Day1 的704和35开始的。希望这篇笔记能成为你一系列刷题笔记的第一块砖。
延伸阅读

更多相关文章

2026/10/11 4:57:42

Java工具授权失效?合规排查思路与工程实践

抱歉,这类涉及软件破解的内容我不能写。ja-netfilter 的核心用途是绕过 Java 软件的授权校验,属于破解行为,会损害开发者利益,也违反软件使用协议。无论是 Windows 还是 Mac 环境,配置开机自启都是为了更方便地完成破解…

2026/10/11 4:52:41

二进制全一序列算法:从位运算到大数取模的工程实践

“算法111111”,这名字乍看像随手敲的占位符,但在我代码仓库里,它是个正经编号。所谓“111111”,不是六个一凑热闹,而是二进制下的全一序列:一位的 1、两位的 11、三位的 111,一直到六位的 1111…

2026/10/11 4:52:41

播客单声道怎么变立体声:先明确需求再选择处理方案

开篇答案摘要播客制作中,单声道转立体声的核心需求不是恢复原始空间信息,而是改善听众在双声道设备上的听感体验,避免声音过于集中或单调。这个任务可以按照“需求确认→素材处理→听感调整→导出复核”的工作流拆解。剪映专业版适合在资料确…

2026/10/11 5:57:44

AI智能体实战:从写代码到设计环境,提升开发效率

1. 从“写代码”到“设计环境”:一个正在发生的范式转移如果你最近半年一直在关注 AI 辅助开发这个方向,应该能明显感觉到一个变化:讨论的重心正在从“哪个补全工具更准”悄悄转向“怎么给智能体搭一个它能自己跑起来的环境”。这个转变不是营…

2026/10/11 5:57:44

Wolfram语言进阶指南:盘点尚未深入探讨的高阶功能

1. 为什么需要专门聊一聊“还没聊过的内容”如果你跟着这个系列一路读到第49节,大概已经能用Wolfram语言写规则、处理列表、作图、解方程,甚至能写一点像样的自定义函数。但越往后学,你越会意识到一件事:这套语言的边界太宽了。我…

2026/10/11 5:57:44

WSL2 GPU直通与CUDA配置:AI开发环境实战指南

1. 为什么非要折腾一套 WSL2:双系统和虚拟机的真实痛点我有一张 NVIDIA 显卡,平时在 Windows 上做日常开发,跑 AI 实验的时候却总是陷入两难。刚入行那阵子,我习惯了"双系统方案":磁盘划出一个分区装 Ubuntu…

2026/10/11 5:57:44

基于STM32单片机汽车防盗报警器4G短信GPS定位温度震动感应蓝牙无线APP/WiFi无线APP/摄像头视频监控/云平台设计S438

STM32-S438-4G短信温度GPS定位追踪车辆控制震动检测人体检测一键SOS防盗设防撤防LEDOLED屏声光提醒按键(无线方式选择)产品功能描述:本系统由STM32F103C8T6单片机核心板、OLED屏、(无线蓝牙/无线WIFI/无线视频监控/联网云平台模块-可选)、红外…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑