ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LC-双指针算法解析:谐振类比、三大模型与LeetCode刷题路线

LC-双指针算法解析:谐振类比、三大模型与LeetCode刷题路线 写“LC-双指针”这笔账很多刷题的人心里其实都有一本。LeetCode上的高频标签里“双指针”永远是被点名的常客可很多人刷了二三十题还是觉得手生题解看得懂自己上手就卡边界或者看到一个新题压根判断不出“这题该不该用双指针”。今天这篇就是想把这笔账摊开来算清楚。我把LC上的双指针题做了一个系统归类又把“双指针”和“lc谐振”“lc滤波”这些同名热词背后那种“频率匹配、收敛共振”的底层直觉打通了——一个是代码层面的指针运动一个是电学层面的能量传递逻辑骨架其实高度相似。文章适合刚入门双指针、急需列出一份可复现刷题路径的人也适合已经刷了不少题但还想把方法提炼成心智模型的进阶者。1. 双指针到底在解决什么从LC谐振看指针收敛的本质很多人第一次听“双指针”以为就是两个下标一左一右往中间走写完几道题之后发现完全不是这么回事。实际上双指针是三类形态各异的方法的统称对撞指针左右夹逼、快慢指针速度不同、滑窗指针一前一后同步平移。这三类形态背后的统一逻辑其实可以拿LC电路里的“谐振”来类比。LC谐振的本质是电感L和电容C之间能量的周期交换。当外加频率等于电路的固有谐振频率时电路呈现纯电阻性信号能量被最有效地传递。这里的“频率匹配”是关键系统最省力的状态就是激励频率和固有频率对齐的那一刻。算法里的双指针本质上也是一台“调频率”的机器指针移动的速度、方向、步调就是你要调的那个“频率”你希望程序用最低的时间和空间成本精确地滑到目标状态或最优解这跟LC电路在谐振点“阻抗最低、能量传输效率最高”的物理图景完全同构。双指针能顺手解决的典型题目都有这些特征问题的可行解空间可以通过某种有方向的收缩来遍历数组或者链表本身具有某种单调性或者问题要求在一段连续的区间内寻找满足约束的子结构。只要命中这些特征就可以尝试用双指针把暴力解下的O(n²)甚至O(n³)复杂度压到O(n)或O(n log n)。这也是为什么面试官那么钟爱双指针题——一道题可以同时考察你“能不能识别单调结构”和“能不能用最优复杂度实现”。我个人的理解是双指针的本质是在“有序假设”下制造信息复用。普通暴力循环每次都是孤立地去尝试一组组合而双指针通过让一个指针“记住”另一个指针已经走过的地方把大量不可能的解空间一次性剪掉。这跟LC滤波器的行为也像LC低通滤波器的截止频率一旦定好高于截止频率的分量会以每倍频程12dB的斜率被衰减分析频率范围瞬间收敛到一个有效通带——双指针做的事情也是在频率域这里是解空间里做一次滤波把注定无效的解直接排除在扫描范围之外。2. 对撞指针最经典的LC“谐振腔”模型2.1 对撞指针的数学基础与适用边界对撞指针又叫左右指针/夹逼指针它的算法骨架相当朴素left, right 0, len(nums) - 1 while left right: # 根据当前结果更新答案 if 条件A: left 1 else: right - 1这个看起来简单到可以背下来的模板真正难的在于“什么时候该移动left什么时候该移动right”。这个决策如果做错整个夹逼过程就像LC电路处于失谐状态能量不能有效传递最后要么得到错误答案要么直接死循环。对撞指针最核心的数学前提是某种“有序性下的单调决策”。经典场景比如“盛最多水的容器”# LeetCode 11. Container With Most Water def maxArea(height): left, right 0, len(height) - 1 best 0 while left right: area min(height[left], height[right]) * (right - left) best max(best, area) if height[left] height[right]: left 1 else: right - 1 return best这个解法为什么可以放心地把较矮的一侧向内移动因为容器的盛水量由短板决定。如果移动较高的一侧容器高度不可能超过较矮侧原来的高度宽度还在减小所以新面积一定不会变大。换句话说以当前较矮的板为边界的“所有可能解”都已经在当前这个状态下被确定了上界不可能再出现更优解所以这些解空间可以直接被剪除。这个剪枝逻辑就是双指针的灵魂。对撞指针容易出现问题的场景就是当数组不具有全局单调性时你却误用了对撞逻辑。比如求“乘积小于K的子数组个数”如果你上来习惯性地用左右夹逼大概率会卡住因为乘积的单调性只体现在“某一侧扩张/收缩”的方向上不是对称的左右夹逼。说到底对撞指针的适用边界是问题本身的可行性随一个指针位置单调变化而且最优解一定可以通过单向收缩找到。2.2 对撞指针的LC谐振类比与几道高频题串讲从LC谐振的角度看对撞指针的收敛过程很像一个RLC并联谐振电路电感L和电容C构成的储能元件在谐振频率附近振荡幅度最大偏离谐振频率后幅度快速衰减。左右指针每次移动就是一次“频率微调”不断把扫描范围向最有可能的最优解“谐振频率”靠拢。左右指针相等的时候相当于系统已经振荡到谐振点所有无效状态被“滤波”干净答案自然浮现。以“三数之和”15题为例它是对撞指针在“两数之和”基础上的扩展。暴力解是三重循环O(n³)先排序后固定一个数把剩余两个数变成“两数之和”问题def threeSum(nums): nums.sort() res [] n len(nums) for i in range(n - 2): if i 0 and nums[i] nums[i - 1]: continue left, right i 1, n - 1 while left right: total nums[i] nums[left] nums[right] if total 0: res.append([nums[i], nums[left], nums[right]]) while left right and nums[left] nums[left 1]: left 1 while left right and nums[right] nums[right - 1]: right - 1 left 1 right - 1 elif total 0: left 1 else: right - 1 return res这个题有三个容易踩的坑排序是前提排序后的有序数组才具备夹逼条件固定i时要跳过重复值这是“去重”的关键步骤找到一组解后left和right各自一定要继续跳过重复值否则就输出重复三元组。很多人死循环就死在这里找到了目标后没有递增/递减指针然后while无脑转圈。“最接近的三数之和”16题也是同一个模板只是更新答案的条件从“等于目标值”变成“更新最小差值”。“有效三角形的个数”611题本质也绕回了两数之和的思路排序后固定最长边c然后left0rightc-1如果nums[left]nums[right]c则left到right-1之间所有数都能构成答案……这道题用双指针的复杂度是O(n²)如果没认清“排序后利用两边之和大于第三边”的单调性写个暴力O(n³)在LC上基本过不了大样本。2.3 对撞指针在字符串场景里的变体对撞不仅适用于数值数组还大量用于字符串判断、回文类题目。比如“验证回文串”125题本质上就是用两个指针从两头扫描过滤掉无关字符后逐一比较def isPalindrome(s): left, right 0, len(s) - 1 while left right: while left right and not s[left].isalnum(): left 1 while left right and not s[right].isalnum(): right - 1 if s[left].lower() ! s[right].lower(): return False left 1 right - 1 return True这里有一个性能小技巧isalnum() 虽然看起来是每次调用开销不大但在极端长字符串上多次调用累加起来也不可忽视。如果你在竞赛或者面试现场有更高的性能要求可以先把字符串清洗成纯字母数字序列再比空间换时间反而更稳。当然刷LC常规解法用isalnum就够了这个问题里更值得记住的是“跳过中间非字母字符”的顺序两个指针必须各自跳过所有非法字符后再判断是否相等——直接在while外层判断会漏掉连续非法字符的情况。字符串对撞类的变体还有“反转字符串中的元音字母”345题这个就是左右指针从两头出发遇到元音交换。另一个很有迷惑性的题是“乘积最大子数组”152题乍一看像滑动窗口实际上因为有负数的存在需要维护当前最大和当前最小两个状态跟对撞指针没有关系。这个题放在这里提醒大家不是所有“一左一右的扫描”都是双指针双指针的界定标准是“指针运动方向有约束、解空间有剪枝”不是仅仅有两个下标。3. 快慢指针链表里的“LC谐振频率”检测器3.1 快慢指针为什么能判定环Floyd判圈算法链表类的双指针题非常依赖“快慢指针”模型其中最经典的是141题“环形链表”。快指针每次走两步慢指针每次走一步。如果链表存在环快慢指针必定在某个时刻相遇如果没有环快指针会先到达末尾。这个结论不是显然的很多初学者问为什么快指针一定要走两步不能走三步五步步长差如何影响判圈的可靠性答案要从循环周期和相位差来想。设环的长度为L慢指针进环时快指针已经在环内距离入环口某位置两者的相对距离模L意义下是某个值d。慢指针每秒走1格快指针每秒走2格相对速度就是每秒1格。也就是说快指针每走两步就相对慢指针拉近1格的距离。无论初始相位差d是多少最多L步之内两者必定相遇。如果快指针每次走3格相对速度变成每秒2格只有当2和L互质时才能保证相遇如果L是偶数而相对速度为2可能永远交错而过无限循环下去。这就是为什么经典的Floyd判圈算法选“快2慢1”而不是其他组合。这个2和1的选择本质上就是在制造一个“谐振频率”匹配两指针的步频差要和环的周长这一“系统固有频率”形成一种必然共振的关系。再看“找到链表中点”876题同样用快2慢1快指针到末尾时慢指针刚好停在中间节点。这一步不仅适用于链表分类、归并分割等场景也是“重排链表”143题这类组合题的前置步骤。143题的做法就是先用快慢指针找中点再把后半部分链表反转最后把两条链表交插合并——一个题目里串了三个经典操作。3.2 环入口位置的计算为什么必然能“解调”出入口141题只问有没有环142题“环形链表 II”更进一步要求找到环的入口节点。这个入口求解过程很像一次“解调”从谐振出发一步步把物理量的相位信息还原到位置信息。设链表的非环部分长度为a环入口到相遇点的长度为b相遇点继续走到环入口的长度为c。因为快指针走的是慢指针的两倍所以快指针路径 a kL b慢指针路径 a b快指针路径始终是慢指针的2倍即2(a b) a kL b移项得到a b kL也就是a kL - b从相遇点M再走c长度到入口而c L - b所以a kL - b (k - 1)L (L - b) (k - 1)L c。这说明从链表头出发的新指针和从相遇点出发的指针在同速前进的情况下一定会在环入口处相遇。这里的代数变换简单但重要面试官很喜欢要求当场推理。我建议刷题时不要只背结论要动手推一遍这个等式推完你会对Floyd判圈算法有完全不同的理解。3.3 快慢指针的删除倒数第K个节点与操作陷阱“删除链表的倒数第N个节点”19题也可以用快慢指针做先让快指针先走N步然后快慢指针同时以步长1前进。当快指针到达链表末尾时慢指针正好指向倒数第N个节点的前驱。此时执行删除操作时要格外小心被删除的是头节点时slow不移动直接返回head.next即可而删除的节点是最后一个节点时fast.next为None的判断条件和slow.next的指向要分清楚。这个题还有一个常常被忽视的问题链表的长度可能小于N吗不会题目保证了N是有效的。但在实际工程化的封装中我会习惯先加一个检查防御性编程能减少大量边界返工。另一个警惕场景是“判断链表是否是回文”234题。常规做法是快慢指针找到中点反转后半段再逐节点比较最后还原链表。这里有一个很隐蔽的问题如果链表节点数为偶数中点的取法会和奇数不同导致反转后的比较范围出错。我建议做题前先手动画两个样例一个奇数长度[1,2,3,2,1]一个偶数长度[1,2,2,1]把指针的终止条件和比较循环的边界都标出来再动手写。这样你的代码基本一次就能跑对不需要反复调试。4. 滑动窗口双指针中的“LC滤波”模式4.1 滑动窗口与技术含量不是所有双指针题都叫滑动窗口滑动窗口严格来说是双指针的一个子类型但有自己独特的运作方式。它的两个指针一前一后通常叫left和right二者的移动方向都是一样的向右窗口始终保持在一个连续的区间上。所有求“最长子串”“最短子串”“子数组最大和”的问题几乎都是滑窗模板的变体。滑动窗口为什么是双指针题里最需要“刻意练习”的类型因为它要求你对“什么时候扩张右边界、什么时候收缩左边界”保持极高的敏感性。这跟LC滤波器的“通带调整”很像滤波器的截止频率决定哪些频率分量能通过滑动窗口的left边界也决定了“哪些元素可以留在当前解候选集合内”。右指针的每次扩张就是让你扫描更多的数据左指针的每次收缩则是“过滤”掉那些已经不再满足约束条件的数据。整个扫描过程像一次不断自适应的滤波输出收敛到最优解。有一套模板我用了很久基本覆盖正弦量、最少覆盖子串、无重复字符最长子串这几道经典题def slidingWindowTemplate(s): n len(s) left, right 0, 0 state {} result 0 while right n: # 扩展右边界 state[s[right]] state.get(s[right], 0) 1 # 收缩左边界直到满足某种约束 while 需要收缩的条件: state[s[left]] - 1 if state[s[left]] 0: del state[s[left]] left 1 # 这里更新答案 result max(result, right - left 1) right 1 return result这个模板的关键点在于“收缩条件”要和题目约束严格挂钩。比如“无重复字符的最长子串”3题收缩条件是“窗口内任意字符出现次数大于1”“最小覆盖子串”76题则需要用两个哈希表和一个match计数来决定何时收缩、何时更新答案。76题是滑窗里的分水岭能独立写出来的人基本对滑窗的“窗口有效性维护”已经过关了。4.2 可变窗口与固定窗口的两类实现差异滑动窗口按照长度是否可变实现思路上有明显差异。固定窗口长度的问题比如“大小为K且平均值大于等于阈值的子数组数目”1343题滑动时只需每次加右侧元素、减左侧元素维护一个sum即可甚至不需要“收缩”这个动作。可变窗口的长度则必须通过“收缩条件”动态决定。举一个可变窗口的高频题“长度最小的子数组”209题def minSubArrayLen(target, nums): left 0 total 0 ans float(inf) for right in range(len(nums)): total nums[right] while total target: ans min(ans, right - left 1) total - nums[left] left 1 return 0 if ans float(inf) else ans这个题是我测试一个人“是不是真懂滑动窗口”的试金石。为什么这里用while而不是if收缩因为收缩一次后total可能仍然大于等于target只有while才能保证窗口收缩到刚好不满足条件为止才能枚举到所有可能的合法窗口。如果把while写成if右边界每扩展一次只收缩一步结果会漏掉最短解。这个问题坑过无数人包括我自己第一次写209题时也栽在这里。另一个值得注意的是“窗口收缩后答案更新的位置”。在209题里答案是在收缩循环里面更新的因为子数组可能缩到比当前更短但在“最长无重复子串”里答案是在收缩循环结束后更新的因为合法窗口正是在“刚好无重复”的状态下达到最长。我见过太多人把答案更新的位置放错导致结果差1或者直接错乱。记住一个口诀最短类问题在收缩时更新最长类问题在收缩完成后再更新。4.3 滑动窗口与哈希表配合状态匹配的艺术很多滑窗题对“窗口内状态”有非平凡的要求需要借助哈希表进行准确的频次匹配。“最小覆盖子串”76题是其中的最高峰。它的标准解法是维护两个哈希表t_count记录目标字符串的字符频次window_count记录窗口内各字符的频次再用一个变量matched表示当前窗口已匹配了多少种目标字符。右指针扩窗时只匹配窗口内新增字符是否使window_count等于t_count如果相等matched加1左指针收缩时如果收缩的字符会让window_count首次小于t_countmatched减1。当matched等于t_count中不同字符的数量时当前窗口就覆盖了整个t。这个题如果你一上来就用“窗口内所有字符出现的总次数大于等于t”这种粗糙判断一定会卡住。原因在于t中重复字符的处理t{a:2, b:1}时窗口只有1个a即使总长度达到4也不满足覆盖条件。所以必须用matched这种“按种类计数”的方法尤其在涉及多个重复字符时它的正确性才立得住。还有一个高频变体“找到字符串中所有字母异位词”438题它的窗口长度固定等于p的长度其实是一个固定窗口哈希表匹配的问题。窗口每次滑一步更新两边的字符计数然后比较两个计数器是否相等。很多时候不需要逐字符比较可以先算一个“有效匹配数”遇到超出p频次的字符就移动左指针这套逻辑可以套进一个模板里。这类题刷多了你会发现滑动窗口哈希表计数匹配其实是LC上字符串双指针题的大半壁江山没有捷径只能靠多写多踩坑。5. 双指针题目的框架化如何把新题归入已知模型5.1 识别题目形态的四步判断法真正到了面试现场或者刷题时碰上一道陌生题你不能指望所有题都刚好是原题。我的经验是四个步骤快速判断是否该用双指针、用哪种双指针。第一步看场景是否为数组或链表且是否有明显的线性扫描需求。如果题目给的是非线性结构树、图双指针通常只是辅助手段不会成为主解法。第二步看数据是否有序或可以预处理成有序。排序是双指针的好朋友对撞指针尤其依赖无序变有序的能力。如果题目中存在“从序列中寻找两个/多个元素满足某种关系”的描述排序后再用指针夹逼往往是一条有效路径。第三步看是否涉及一段连续区间、子数组或子串。连续区间的约束越强滑动窗口越可能是首选。尤其是“最长/最短/恰好”这类句式滑窗的匹配度高得惊人。第四步看是否有环、回文、中点等结构特征。出现这些词时优先考虑快慢指针。这四个步骤不是绝对准确的算法但它们能把“我要不要想想双指针”这个问题从玄学变成流程化判断。我专门把这套判断法做成一张自查表在企业和面试培训时也分享过反馈很好。5.2 复杂度证明为什么双指针一定是“省”的双指针可以被广泛用于优化复杂度的根本原因在于它让每个元素最多被访问常数次。以滑窗为例right指针不断右移每个元素入窗一次left指针也只会右移每个元素出窗一次。两个指针总共移动O(n)次因此时间复杂度严格O(n)。对撞指针同理left和right每一次移动都让区间长度减1总共不会超过n次移动也是O(n)。链表快慢指针更是从头到尾扫一遍O(n)时间O(1)空间。对比暴力法两数之和暴力解O(n²)三数之和暴力O(n³)滑动窗口暴力全子串枚举O(n²)。时间复杂度上双指针几乎是指数级的压缩。这里还有一个小的空间优化点大多数双指针都只需要O(1)额外空间这是口试时经常被追问的“能否优化空间”问题的最佳答案。我见过很多人明明双指针写出来了却另外开了一个O(n)的哈希表存状态把双指针最值得夸耀的优势白白放弃了。能用O(1)空间的题目尽量不要开额外大数组。5.3 从双指针到“双指针排序”再到“双指针二分”的拓展双指针很少孤立使用。最常见的是先排序再用双指针。排序本身代价O(n log n)但能把后续的搜索维度降一层。“两数之和”题目如果要求返回下标先排序会丢失原始位置信息所以只能哈希但“三数之和”不要求返回最原始索引只要求返回数值排序就是有效预处理。遇到这种“数值型答案vs原始下标型答案”的抉择决定你是否能排序后用双指针也是最常见的一个决策点。还有一些题目需要“双指针二分”的双重结构比如“最长重复子数组”718题。核心思路是把问题转化为“判断是否存在长度为K的公共子数组”这个判断过程用滑窗思想哈希即可但对K本身做二分搜索。也就是说外层二分枚举答案长度内层用类似滑动窗口的双指针技巧验证整体复杂度O(n log n)。这种排列组合思维是后面刷高级一点的LC题必须掌握的技能。6. 经典LC双指针题目清单与刷题路线建议6.1 按难度和场景组织的高频题清单我整理了一份自己刷过且验证有效的LC双指针路线按类型分组每组内部按难度递增排列适合做成分阶段刷题计划对撞指针方向125 验证回文串入门对撞字符串过滤167 两数之和 II - 输入有序数组入门有序数组两数之和11 盛最多水的容器基础移动矮侧15 三数之和进阶排序去重对撞16 最接近的三数之和进阶维护全局最小差值611 有效三角形的个数进阶固定最大边夹逼快慢指针方向876 链表的中间结点入门快2慢1找中点141 环形链表入门判环19 删除链表的倒数第N个结点基础快指针先走N步142 环形链表 II进阶FLoyd推导入口234 回文链表进阶找中点反转比较143 重排链表高难多操作串联滑动窗口方向3 无重复字符的最长子串入门最长无重复滑窗209 长度最小的子数组入门最短子数组滑窗76 最小覆盖子串高难双哈希表状态匹配438 找到字符串中所有字母异位词基础固定窗口计数匹配567 字符串的排列基础滑动窗口判定排列这份清单刷完大概就20题出头。它的好处是每一组题之间都有极强的迁移性做完15题再去做16题基本就是改一行代码的时间做完141再去做142能帮你把FLoyd判圈算法真正内化。6.2 刷题方法论与复盘策略刷双指针和刷其他算法题最大的不同在于它不像动态规划那样需要Aha Moment你甚至可以说双指针题“怎么想都不会差太远”第一反应就应该是排序、扫描、移动指针。所以刷题策略的核心不是苦思冥想而是大量暴露不同形态的题目建立“题感”。我建议每个类型连续刷5到6题中间不要穿插DP或图论这样大脑会快速归纳出模板。复盘时我会用一个小技巧每一道双指针题在题解旁边手写三句话——第一句是“怎么想到用双指针”第二句是“两个指针分别在什么时候移动”第三句是“答案更新发生在哪个位置”。这三句话写下来几乎等于把这道题压缩成了一个可复用的记忆单元。下次遇到相似题目先回想这三句话再比对当前题目的约束差异思路会清晰很多。这个方法听起来简单但坚持十几道题后双指针题的识别速度会明显提升。6.3 现场手撕双指针的代码习惯面试现场写双指针题代码的“可读性”比竞赛代码更重要。我建议遵循三个习惯。第一就是命名。left和right、slow和fast、windowStart和windowEnd比i和j的语义强得多。即使现场写快一点也至少用l和r这种能自解释的缩写。第二是边界条件的检查。链表操作前先确认head是否为null数组下标检查时先考虑right等于len(nums)-1还是len(nums)。第三是循环不变量的注释。在代码开头写“窗口始终满足X条件”“区间[left, right]为当前候选解”这类注释能大幅减少写错边界和分支的概率也方便面试官理解你的思路。这三个习惯在平时刷题时就刻意培养到了真正手撕的时候你会发现自己的调试时间至少减少一多半。7. 高频报错与边界条件排查手册7.1 双指针最容易翻车的五类错误我把自己刷题和帮人改代码过程中遇到的高频错误整理成一张问题排查表。这五个错误覆盖了90%以上的双指针submit失败原因。错误类型典型表现排查思路死循环运行超时while永远不结束检查找到条件更新后指针是否真的移动了检查while结束后left和right是否还满足条件下标越界IndexError/数组越界检查访问数组元素前是否先判断了指针位置滑窗右指针访问前确认right小于n指针移动顺序错误结果差1或整体错位先更新答案再移动指针还是先移动再更新答案要和题目的语义对齐窗口收缩没写while结果偏大或偏小209题、76题必须收缩到条件不满足为止if只能收缩一步去重逻辑缺失输出重复答案三数之和找到一组解后左右指针都要跳过所有相邻重复值这张表我建议存下来每次提交失败后对着表看一眼马上能定位问题大概出在哪一类。我见过大量新人卡在一个错误类型上很久一旦有了这张表定位问题的效率会翻倍。7.2 实战排错案例以76题和143题为例先说76题的典型排错场景。很多人第一次写“最小覆盖子串”时把收缩条件写成了“这样收缩后还能不能覆盖目标”然后在收缩循环里反而不断加回字符导致死循环。正确做法是先扩张右边界把新字符计入window_count再判断是否已经“覆盖”matched need_size覆盖时尝试收缩左边界并同步更新window_count与matched收缩到“刚好不再覆盖”时停止记录一次答案。这个流程的每一步都是在“更新状态、再判断”状态更新和判断的顺序千万不能乱。再说143题的排错。这个题需要三个子步骤找中点、反转后半段、交叉合并。每一步单独写都能写对串起来就各种边界问题。我踩过的坑是找中点时用slow和fast两个指针fast每次走两步当fast.next为null时slow到底落在哪个节点偶数节点时slow会偏右。如果后续反转后半段用的是slow.next就要明白这里是从中点之后的节点开始反转如果直接把整个后半段反转中间节点会被处理两次。建议每一步都打印链表的状态确认“当前处理到哪一节”再拼接。链表的题最可靠的方式就是画图、画指针、画next指向。7.3 边界条件速查表int溢出与空值处理双指针题还容易在边界上翻车而有时候不是指针逻辑错了而是类型问题。比如“盛最多水的容器”中面积 高度差乘以宽度如果数组长度很大、高度很大用int计算时可能溢出。LC题目通常用Python大整数或者C long long就能解决但C选手要养成好习惯计算面积前先把height转成long long或者乘法前显式断言类型。空值处理上链表题几乎每个题都要问一句“head为null怎么办”。在141题里head为null时直接返回false19题中如果删除的是头节点需要返回head.next143题中如果链表只有1个或2个节点就直接返回原链表。数组题也有空数组判断数组是否为空为空时返回0或空列表不要尝试访问nums[0]。这些看起来琐碎但每一次都决定你是白板一遍过还是反复提交。8. 从LC双指针到真实工程类比不是为了炫技最后聊聊“LC”这个词的双重身份。在这篇文章里“LC-双指针”既是LeetCode平台上的高频标签也是电学里电感L和电容C的代号。我在解释双指针时反复用“谐振”“滤波”做类比不是想卖弄跨学科知识而是因为这两个领域在思维结构上确实共享同一个内核都是一种“通过调整某个参数让系统收敛到最优状态”的过程。LC电路的谐振频率由L和C的值决定双指针的“收敛频率”由指针移动条件和目标状态的匹配度决定。一边是物理系统的能量集中一边是算法系统的搜索空间剪枝两者异曲同工。真实工程中双指针思想也不只出现在LeetCode题解里。字符串解析里常见的双端扫描、数据流里的滑窗统计、日志分析里的时间窗口聚合甚至分布式系统里两阶段提交的游标推进都有双指针的影子。我在工作中处理超长日志的连续时间段计数时就是用滑窗思想的变体把O(n²)的暴力扫描降到了O(n)效果立竿见影。这种从刷题到工程的能力迁移才是“LC”这个标签真正值钱的地方。我个人刷双指针系列时最深的体会是这类题不适合“题海战术”也不适合“死磕一道题”。最好的节奏是按类型分组连续刷五六道每道都写出来、跑通、复盘三句话然后隔一周再重刷一遍。第二次刷的时候试着不看任何参考直接在白纸上从零写完整解法。你会发现第二次的速度和准确率相比第一次是质的飞跃而且写起来会有一种“顺着模板走”的顺畅感。这种顺畅感就是你建立双指针心智模型的最好证明。
返回列表