资讯动态

刷题复盘:五道简单题吃透前缀和、栈、双指针与链表哨兵

发布时间:2026/10/13 7:38:02 来源:尧图企业网站定制
很多第一次打开算法题库的人都会默认“简单题”等于“送分题”。我也曾经这么以为直到在某道字符串题上连续踩了三十分钟的坑才开始重新审视“简单题”这三个字。这篇刷题记录是我在“简单题”标签下完成到第5篇时的复盘一共五道题分别覆盖前缀和、栈、双指针、哈希表、链表哨兵这几个基础知识点。每道题我都会写清楚题目要求、我的思考过程、最终的AC代码以及复盘时发现的坑。适合刚开始刷算法题的新手也适合想快速找回手感的开发者参考。1. 从“第五期”聊起为什么还在刷简单题1.1 简单题不等于送分题可能有人会问都第五期了怎么还在刷简单题说实话我自己也在刷题中期突然冒出过这个念头后来发现真正卡住我的不是难题的算法设计而是一些简单题里藏着的边界条件。比如后面会讲到的“相邻重复字符删除”解法无非就是栈但如果你没有想清楚“删除之后左右两边可能再次形成相邻重复字符”写出来的第一版几乎必然有问题。简单题只是入门的门槛低并不代表解题时不需要严谨。它们往往把一两个知识点讲透让你在最小范围内体会“边界条件”的重要性。对新手来说这是最好的起点对有经验的人来说这是最容易忽视的地基。1.2 我挑选这五道题的标准这五道题并不是随机抓来的我刻意选了五个不同的考察点前缀和、栈、双指针、哈希表、链表哨兵。前四道属于数组和字符串类最后一道是链表。挑选标准有三个第一学了之后能用到很多其他题目里复用价值高第二代码量通常不超过二十行但边界条件值得研究第三每一题都能引申出一个通用套路我会在复盘里把套路抽出来说清楚而不是只贴一段代码。这几道题的难度都控制在三四十分钟内能AC的范围刚好适合作为“手感恢复剂”。如果你正在从简单题入门阶段过渡到中等题这一期记录应该能在思路上给你一些参考。2. 题一区间和查询背后的前缀和思想2.1 题目复述与暴力解题目大概是这样的给一个整数数组nums然后有m次询问每次询问给两个下标l和r要求返回闭区间[l, r]内所有数字的和。比如nums [1, 3, 2, 5, 4]询问(1, 3)结果就是3 2 5 10数组下标从 0 开始。看到这个题大多数人第一反应就是循环累加def range_sum(nums, l, r): total 0 for i in range(l, r 1): total nums[i] return total这段代码本身没有逻辑错误问题出在复杂度。如果nums长度是 10 万询问也是 10 万最坏情况下每次查询都要遍历整个数组总的操作量会到 10^10 级别想不超时都难。这也是为什么“简单题”也会强调算法复杂度——它不考高级数据结构但要求你意识到重复计算可以被消除。2.2 前缀和数组的构建逻辑前缀和的核心思想是在询问之前做一次预处理把从 0 到当前位置的和全部算好之后查询某一段的和就只需要两次“减法”。设prefix[i]表示nums[0]到nums[i-1]的和注意这里是一个长度为n 1的数组这样prefix[0] 0作为“空区间”的和方便处理l 0的情况。构建过程用递推prefix [0] * (len(nums) 1) for i in range(1, len(nums) 1): prefix[i] prefix[i - 1] nums[i - 1]查询闭区间[l, r]的和时只需要def query(l, r): return prefix[r 1] - prefix[l]拿前面的例子验证nums [1, 3, 2, 5, 4]构建出来的前缀和是[0, 1, 4, 6, 11, 15]。查询(1, 3)prefix[4] 11prefix[1] 1差值是 10正确。2.3 为什么是“n1”而不是“n”这个n 1是第一次写的时候最容易忽略的细节。如果直接建一个长度等于n的数组查询[0, r]时就得单独判断l - 1 0或者写prefix[r] - (prefix[l - 1] if l 0 else 0)代码会变丑也容易踩边界。用n 1的写法本质上是把“下标”和“前多少个元素”这两件事分开prefix[i]表示“前 i 个元素的和”。这样[l, r]的和就是“前 r1 个元素之和”减去“前 l 个元素之和”语义非常干净不需要任何特判。2.4 复盘心得先写暴力再优化这道题我录完代码之后的体会是简单题别上来就优化。先写一个暴力版本跑一遍确认你理解了题意然后再想“哪里重复计算”。大部分前缀和、双指针、哈希表的题都是从暴力解出发自然推导出来的。如果你一开始就跳到优化解很容易知其然不知其所以然隔两天再见到同类题还是不会。3. 题二相邻重复字符删除的栈操作3.1 题目复述与直觉题目是给定一个字符串s不断删除相邻且相同的两个字符直到字符串里没有相邻且相同的字符为止返回最终字符串。例如s abbaca过程是删除中间的bb得到aaca此时aa变成相邻重复字符删除后得到ca最终答案是ca。第一眼看上去很多人会想写一个while循环在循环里replace(aa, )或者手动删除连续字符。这个思路很危险replace一次会替换所有匹配项但删除后新增的相邻关系不会被二次处理手动删除又会遇到下标动态变化的问题调试起来非常难受。3.2 用栈模拟删除过程正确的姿势是维护一个栈。从左到右扫描字符串如果当前字符和栈顶相同说明它们相邻且重复把栈顶弹出去否则将当前字符压入栈。由于每次弹出之后新的栈顶又会和后续字符重新比较所以“删除后产生新的相邻重复”这个性质被自动处理了。代码非常短def remove_duplicates(s): stack [] for ch in s: if stack and stack[-1] ch: stack.pop() else: stack.append(ch) return .join(stack)用abbaca模拟一遍读 a - 栈 [] 读 b - 栈 [a] 读 b - 栈顶是 b相等弹出 - 栈 [a] 读 a - 栈顶是 a相等弹出 - 栈 [] 读 c - 压入 - 栈 [c] 读 a - 压入 - 栈 [c, a] 结果 ca3.3 容易踩的坑别用字符串替换我第一次写这题时没有用栈而是想在while循环里s s.replace(aa, )。这看起来很像但结果是错的。因为replace会一次性把所有“非重叠”的aa都删掉比如aaaa执行一次replace(aa,)结果是但它已经跳过了“先删前两个再删中间两个变成空串”的完整过程。且如果下一次循环又生成新的aa逻辑会变得更复杂。所以这题的正确模型是“栈顶比较”而不是“模式替换”。这让我意识到哪怕是一道简单题也要在动手前想清楚数据变化的状态是“每次只处理最相邻的一组”还是“一次处理所有匹配项”这两个模型不同对应的算法完全不同。3.4 从“栈”到“状态机”后来我复盘时把这题归类到了“状态机”思想当前状态栈顶字符决定下一个字符该合并还是该保留。以后遇到很多括号匹配、相邻冲突、操作流水线类的题都可以套这个模型。一道简单题能学到这种抽象性价比非常高。4. 题三、题四双指针与哈希表的组合拳4.1 合并两个有序数组双指针的入门题目给定两个升序数组nums1和nums2返回一个升序合并后的新数组。例如nums1 [1, 3, 5]nums2 [2, 4, 6, 8]结果是[1, 2, 3, 4, 5, 6, 8]。看到“升序”两个字第一反应应该是双指针。用两个指针分别指向两个数组的当前元素比较它们的大小把较小的放进结果数组然后对应指针向后移动。当其中一个数组遍历完把另一个数组剩余部分直接拼接def merge(nums1, nums2): i j 0 res [] while i len(nums1) and j len(nums2): if nums1[i] nums2[j]: res.append(nums1[i]) i 1 else: res.append(nums2[j]) j 1 res.extend(nums1[i:]) res.extend(nums2[j:]) return res很多同学可能会想直接用列表的sort不就行了sorted(nums1 nums2)一行搞定。这么写当然也能过但就失去了练习双指针的机会。双指针的复杂度是O(n m)而排序是O((n m)log(n m))在数据量大时差距很明显。更重要的是双指针是后续归并排序、链表合并、区间合并等一堆题的基础。4.2 两数之和哈希表如何用空间换时间题目给定整数数组nums和一个目标值target找出和为target的两个不同下标返回它们的下标。例如nums [2, 7, 11, 15]target 9返回[0, 1]。暴力是两层循环。进阶思路是遍历数组时对于当前数x我们想知道target - x是否在之前出现过。哈希表可以做到 O(1) 的查找于是一次遍历就够def two_sum(nums, target): seen {} for i, x in enumerate(nums): y target - x if y in seen: return [seen[y], i] seen[x] i return []这里有个关键细节一定要先检查y是否存在再把当前x放入哈希表。如果先放后面遇到x target - x时会误把当前元素本身当成答案比如nums [3, 2, 4]target 6如果先放会错报[0, 0]。4.3 双指针和哈希表怎么选这两道题放在一起复盘是因为它们很有代表性双指针适合输入本身有序或者可以排序的场景空间复杂度 O(1)哈希表适合需要快速查找“之前看过什么”的场景时间复杂度 O(n)但空间是 O(n)。如果同一道题数组无序且要求返回下标通常用哈希表更稳如果数组有序双指针往往更优雅。刷题多了之后你会发现简单题里反复出现的不是某个算法而是这种选择逻辑。5. 题五链表删除目标值的哨兵节点5.1 题目与链表删除的经典困扰题目给定单链表头节点head和一个整数val删除链表中所有值等于val的节点返回新的头节点。链表删除的通用思路是找到目标节点的前一个节点prev然后把prev.next指向目标节点的下一个节点。但这里有一个天生的麻烦如果要删除的是头节点它没有前驱节点。很多人会写一堆if head.val val: head head.next的分支代码很容易漏。5.2 哨兵节点解法更优雅的做法是在真正头节点前面再加一个“哨兵节点”也叫哑节点dummy。dummy.next head然后统一从dummy开始遍历。这样无论删除的是头节点还是中间节点都有统一的前驱处理方式def remove_elements(head, val): dummy ListNode(-1) dummy.next head prev dummy cur head while cur: if cur.val val: prev.next cur.next else: prev cur cur cur.next return dummy.next注意一个细节当删除节点时prev不要移动。因为删掉当前节点后prev.next会指向下一个节点而下一个节点可能仍然是目标值需要继续判断。只有当当前节点不是目标值时prev才向前移动。这个“删除时不移动 prev保留后续指针”的习惯能避免大批链表题里的 bug。5.3 简单题里的链表边界训练做了这道题之后我才发现简单题里的链表题练的其实是“对空指针和头节点的敏感度”。比如空链表dummy.next是None不会出错所有节点都是目标值最后返回dummy.next也是None尾节点是目标值删除后要手动把prev.next None代码里已经通过cur.next天然实现。如果不用哨兵节点这三种情况每一种都得单独写特判。所以我个人强烈建议新手从第一道链表题开始就养成用哨兵节点的习惯不要觉得“多加一个节点很奇怪”。它最常见的现实例子就是给队列加一个空的队头哨兵从而简化边界判断。6. 三处翻车现场与我的复习SOP6.1 坑一前缀和查询的下标偏移第一道题我翻过车建前缀和数组时长度用了n然后查询(l, r)写成了prefix[r] - prefix[l - 1]。当l 0时prefix[-1]取到了数组最后一个元素结果完全错误。后来改成n 1和prefix[r 1] - prefix[l]才彻底解决。这个坑告诉我们数组下标不是从语义上理解而是从“索引位置”和“元素数量”两个角度理解。前缀和里prefix[i]的语义是“前 i 个元素的和”只有当i可以等于 0 时才能优雅表达“前 0 个元素的和”。这提醒我以后设计辅助数组时先问一句边界下标语义是什么6.2 坑二栈不等于字符串替换第二道题的翻车我前面已经提过用replace处理连续删除会得到错误结果。更深层的教训是看到“相邻”“连续”“删除后重新比较”这几个关键词要自动联想到栈。栈不只是一种数据结构也是一种“最近的状态最优先被处理”的模型。还有一次我写栈解法时把判断条件写成了if ch in stack变成了“只要字符在栈里就弹出”而不是“栈顶等于当前字符”。这导致输入稍微复杂一点就输出错得离谱。所以一定要记住栈的动态位置只关心栈顶不关心栈里的其他元素。6.3 坑三链表题里过早判断空头写链表删除时一开始我在函数开头写了if head is None: return head然后在循环里处理。这个写法本身没错但它让我在实现时总想着“头节点特殊处理”。用了哨兵之后连这个开头判断都可以省掉代码反而更统一。我复盘时意识到很多边界代码不是“必需”的而是“思路没到位时的补丁”。如果你发现自己写了一大堆if处理头、尾、空大概率是缺少一个统一结构。链表题的哨兵、数组题的前缀占位、树题里的虚拟根节点都是同一个道理。6.4 可复用的复习SOP这几期简单题刷下来我总结了一套自己的复习流程分享出来每次 AC 之后不急着看下一题先在记录表里写三行内容题目关键词、核心解法、风险点一周之后只根据“题目关键词”重建代码要求自己三分钟内写完整如果三分钟写不出来说明这道题只是“看过答案”而不是“会了”把它放回待复习队列等累计到 20 道题后不按题目顺序而是按“解法类型”重新分组把区间和、栈、双指针这类题放到一起看归纳模板。这套流程让我把“简单题”刷出了“中等题”的效果。很多你以为已经会的东西一旦脱离题解默写就会原形毕露。而简单题恰好是练习默写的最小成本场景。如果你也卡在简单题反复 AC 又反复忘的状态不妨试试这个复习 SOP。等基础扎实了再往中等题推进时你会发现真正难的不是“没见过这个算法”而是“不知道什么时候该用它”。而这件事往往就是在那些不起眼的简单题里慢慢养成的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑