资讯详情

力扣刷题记录:四道经典简单题,掌握栈、动态规划与哑节点模板

📅 2026/10/7 22:46:50 | 华诺云谱 👁 阅读
力扣刷题记录:四道经典简单题,掌握栈、动态规划与哑节点模板
力扣刷题记录系列终于写到第5篇了。后台一直有朋友问为什么总在简单题里打转不直接上难题这篇就是答案。简单题不是用来凑数的它是面试保底能力、难题思维拆解的最小单位也是我每天保持手感的热身操。这次记录了回文数、有效的括号、爬楼梯、合并两个有序链表四道题都是力扣里经典到不能再经典的简单题。我会把每道题的思考过程、边界条件、实现代码、复杂度以及我在提交时踩过的坑全部展开顺便复盘几个高频题型的通用模板适合刚开始刷力扣的新手也适合刷了一段时间想回去补基础的人。1. 为什么还在坚持刷简单题选题逻辑与心法1.1 简单题才是一线面试的地基很多人刷力扣有个误区觉得简单题没含金量直接冲中等题、困难题才有成就感。我见过不少朋友把热题100从头刷到尾回头做一道删除有序数组中的重复项依然会卡住。原因很简单难题本身不是独立存在的它是若干简单题的知识点组合起来的。比如合并K个升序链表看起来是困难题但你把问题拆开底层就是合并两个有序链表这个简单题动作的重复。地基不稳楼层再高也晃。一线面试里真正高频出现的题目往往就是简单题和中等题。面试官要的不是你背过多少难题而是你能不能在一小时内把一个基础问题讲清楚、写干净、覆盖边界。回文数、有效的括号这类题目恰恰是考察逻辑清晰度和代码基本功的标尺。把简单题刷透比囫囵吞枣刷十道难题有用得多。另外从心态角度说简单题也是建立自信的路径。我自己的经验是连续几天做不出题的时候拿几道简单题出来找回手感状态恢复得非常快。刷题记录这件事不是只有哇这道题好难才算记录把简单题做漂亮同样值得写下来。1.2 简单题的三种刷法拒绝无效勤奋想从简单题里拿到真正的收益光AC了就翻篇是不够的。我这里总结了自己常用的三种刷法分享给各位参考。第一先独立思考再动手。不夸张地说很多简单题你觉得自己会其实只是看题解觉得会。我的规矩是拿到题目先给自己5到15分钟的独立思考时间哪怕最后思路是错的这个思考过程也比直接抄答案有价值。因为只有卡过壳你才知道这道题的思维障碍点在哪里下次才能避开。第二一题多解把简单题吃透。同一个题目能用字符串解能不能用数学解能用迭代解能不能用递归解比如回文数最直观的思路是转成字符串但如果你进一步想整数反转、想只反转一半对边界条件的理解就完全不一样了。简单题最大的价值就是空间小适合反复推敲一次把一个知识点揉碎。第三刷完要总结模式而不是记题号。题目是刷不完的但题型是有限的。比如看到相邻匹配就想到栈看到有序数组去重就想到双指针看到链表头部可能被改就想到哑节点。形成这种条件反射比做100道题更重要。这个系列能一直写下去核心驱动力也是想把这些模式越攒越厚。2. 四道简单题的完整拆解从读题到AC2.1 回文数用反转一半来规避整数溢出先聊回文数这道题。题目本身很直白给你一个整数 x判断它是否是回文整数。121 是回文-121 不是10 也不是。大多数人的第一反应是转字符串反转后比较。这个思路完全正确代码也很好写但是有一个小问题它需要额外的 O(n) 空间。对于这道题来说没有毛病勉强算是最优解之一。可如果面试官追问能不能空间 O(1) 解你就需要换思路了。我推荐的做法是反转一半。听起来玄乎其实核心就一句话既然回文数正着读和倒着读一样那我只需把数字的后半段反转过来和前半段比一比。举个例子x 1221后半段反转后是 12前半段是 12相等所以它是回文数。那怎么确定反转到了一半呢我的习惯是写一个循环每次把 x 的末位剥离出来翻转到 reverted 变量上同时把 x 整除 10。循环的条件是 while x reverted当 x 已经小于等于 reverted 时说明我们已经处理到中间位置了。这里有个很重要的细节数字位数是奇数还是偶数会导致结果不同。偶数位时比如 1221循环结束时 x 12reverted 12直接判断 x reverted 就行。奇数位时比如 12321最终 x 12reverted 123这时候要让 reverted 整除 10 再比较也就是把中间那位多余的数丢掉。再来看两个容易翻车的边界。负号不算数字的一部分所以 x 0 直接返回 False。还有个容易被忽略的如果 x 的个位是 0 且 x 本身不是 0那它也不可能是回文数因为数字不能有前导 0。比如 10 和 100反转后首位是 0根本不合法。参考代码def isPalindrome(x: int) - bool: if x 0 or (x % 10 0 and x ! 0): return False reverted 0 while x reverted: reverted reverted * 10 x % 10 x // 10 return x reverted or x reverted // 10时间上每次循环让 x 缩小十倍所以时间复杂度是 O(log₁₀n)空间复杂度做到 O(1)。这道题我每次重新做都会刻意不用字符串写法目的是强化对整数运算的感觉。顺便说一句很多力扣题目把能不能用现有 API 一把梭视为一种能力但把底层逻辑写出来才说明你是真的懂。2.2 有效的括号栈的消消乐是怎么运作的有效的括号在我看来是栈这种数据结构的入门拜师题。题目给你一个只包含括号字符的字符串让你判断括号是否按正确顺序闭合。()[]{} 是合法的([)] 不合法。这题我第一次做的时候居然卡了很久原因是我总想着用计数器来匹配后来发现括号不是一个类型计数器根本解决不了嵌套和交叉的问题。后来想明白了括号匹配本质上就是后出现的左括号先被右括号消掉也就是后进先出天然就是栈的结构。过程是这样的从左往右遍历字符串遇到左括号就压栈遇到右括号就检查栈顶是不是对应的左括号。如果是就弹出栈顶两边抵消如果不是说明交叉匹配了直接返回 False。所有字符处理完之后栈为空才是有效括号串栈里还有残余左括号也说明无效。实现上有两个细节值得注意。一是当栈为空时遇到右括号比如字符串开头就是 )这时候不能去取栈顶要单独处理。二是匹配关系不要写一堆 if else用一个字典把右括号映射到左括号代码会清爽很多def isValid(s: str) - bool: stack [] mapping {): (, ]: [, }: {} for ch in s: if ch in mapping: if not stack or stack[-1] ! mapping[ch]: return False stack.pop() else: stack.append(ch) return not stack这道题背后其实藏着一个很形象的消消乐思想。括号配对本质上就是相邻的一对匹配元素互相抵消。理解了这一点你会发现它变体无数比如删除字符串中的所有相邻重复项、行星碰撞、甚至计算器的括号处理内核都是同一套逻辑。我刷到后面越来越觉得题目可以变但套路是不变的。2.3 爬楼梯一道题讲透递归、记忆化与滚动数组爬楼梯这道题的文字描述特别简单你正在爬楼梯每次可以爬 1 阶或 2 阶问爬到第 n 阶有多少种不同方法。第一次接触的人很容易直接想递归。思路也顺理成章到第 n 阶的最后一步要么是从 n-1 阶跨 1 阶上来的要么是从 n-2 阶跨 2 阶上来的。所以方法数 f(n) f(n-1) f(n-2)。递归写起来两行但一提交就超时。为什么因为你把同一个子问题算了很多遍。你可以画一棵递归树n5 的时候 f(2) 会被重复计算好多次。n 一旦到 40 以上暴力递归的复杂度直接指数爆炸。接下来很多人会想到记忆化搜索也就是拿一个数组或字典把已经算过的 f(k) 存起来下次直接查。这个思路没问题在递归的框架内已经能通过力扣了。但面试官往往还会追问一句能不能把空间优化掉答案是用滚动数组。既然 f(n) 只依赖前两项我就只保留两个变量边算边更新。你不需要记录全部的 f(1) 到 f(n-2)因为它们在计算 f(n) 的路上已经没用了。空间从 O(n) 降到了 O(1)。代码如下def climbStairs(n: int) - int: if n 2: return n prev, cur 1, 2 for _ in range(3, n 1): prev, cur cur, prev cur return cur这道题我至今仍觉得它是动态规划最好的入门题目之一因为它把状态定义—转移方程—初始条件—遍历顺序这四个 DP 的要素全讲清楚了。dp[i] 代表到达第 i 阶的方法数dp[i] dp[i-1] dp[i-2]dp[1] 1dp[2] 2然后从 3 开始向后推。以后你遇到再复杂的 DP 题本质上也离不开这几个步骤只是状态和转移变复杂了而已。实际做的时候要注意 n 2 的特殊情况很多边界 bug 都是在这种小数值上翻车的。另外如果题目改成一次可以爬 1 到 m 阶解法就变成前缀和优化问题了这也是从这道简单题延伸出去的一个很好的思考方向。2.4 合并两个有序链表哑节点解决头指针难题合并两个有序链表是链表题里最基础的送分题但也是很多新手第一次被头指针问题搞到崩溃的题。题目给你两个升序链表要合并成一个新的升序链表。思路其实很好说同时遍历两条链表谁的值小就把谁接进结果链表然后移动对应指针。某个链表先走完直接把另一条剩余部分接上。难点在于新链表的头节点该怎么处理如果你一开始就 new 一个节点当作开头最后返回的时候容易多出一个头如果你不提前建节点遍历之前又得单独处理结果链表为空的分支代码写得很丑。我用的方案是哑节点也叫哨兵节点。先创建一个 dummy 节点当作占位真正的头节点是 dummy.next。这样在循环里我可以无脑让 cur.next 指向符合条件的节点cur 再往后移动完全不需要判断当前是否是第一个节点。遍历结束后把没走完的那条链表直接拼上最后返回 dummy.next 即可。def mergeTwoLists(l1: ListNode, l2: ListNode) - ListNode: dummy ListNode(-1) cur dummy while l1 and l2: if l1.val l2.val: cur.next l1 l1 l1.next else: cur.next l2 l2 l2.next cur cur.next cur.next l1 if l1 else l2 return dummy.next这个哑节点技巧不是只在这一道题里有用它几乎适用于所有需要重新构造链表或者链表的头节点可能被改动的场景。比如删除链表的倒数第 N 个节点、反转链表的区间、两两交换链表中的节点都能用 dummy 来统一处理边界。我自己的体会是简历上写着熟悉数据结构很简单但真正在写链表题时不慌靠的就是这种熟极而流的小套路。四道题都聊完了最后用一张表把复杂度收拢一下方便大家回头复习题目核心数据结构/思想时间复杂度空间复杂度回文数整数反转、数学O(log₁₀n)O(1)有效的括号栈、哈希映射O(n)O(n)爬楼梯动态规划、滚动数组O(n)O(1)合并两个有序链表链表、哑节点O(mn)O(1)3. 刷题高频考点提炼三个可以直接套用的模板刷题记录写得多了以后我有一个很明显的感受简单题刷到一定程度你记住的不是题目本身而是看到某种特征就立刻掏出一套模板的反射。下面这三个模板是我最近总结出来的不敢说覆盖所有情况但对新手来说绝对够用。3.1 栈匹配模板看见成对消除就想到栈栈这类题的特征非常明显——元素之间存在配对消除最近关联的关系。我给出的通用套路是遍历序列如果当前元素能和栈顶元素发生匹配或抵消就弹出栈顶否则把当前元素压入栈。最后根据题目要求检查栈的状态。stack [] for item in data: if stack and should_eliminate(stack[-1], item): stack.pop() else: stack.append(item) # 视题目要求返回 len(stack)、not stack 或具体结果这个模板能直接套用的题很多有效的括号删除字符串中的所有相邻重复项比较含退格的字符串甚至每日温度里的单调栈都可以从这个框架延伸出去。关键是你要打磨好那个 should_eliminate 的判断逻辑比如括号题用的是字典映射相邻重复题用的就是直接相等剩下的结构几乎一样。3.2 双指针原地覆盖模板有序数组去重的标准打法很多数组类的简单题要求你原地修改数组比如删除有序数组中的重复项。这类题的通用模板是快慢指针慢指针指向可覆盖的位置快指针负责遍历。如果发现当前元素和慢指针前一个元素不同说明这是一个新元素把它覆盖到慢指针位置慢指针前进一位。if not nums: return 0 slow 1 for fast in range(1, len(nums)): if nums[fast] ! nums[slow - 1]: nums[slow] nums[fast] slow 1 return slow为什么慢指针从 1 开始因为第一个元素无论如何都该保留覆盖也没有意义。这个模板的变形也很多比如移除元素这道题思路完全一样只是比较对象从前一个元素换成目标值。双指针的巧妙之处在于用一个循环完成两个循环的工作既省时间又省空间面试官看到这种写法通常都会点头。3.3 链表哑节点模板统一处理头节点的修改刚才在合并两个有序链表里已经用过一次了这里再单独拎出来讲是因为它太常用了。凡是涉及新建链表头节点可能变化链表长度变化的题目我都建议先造一个哑节点dummy ListNode(0) cur dummy while 节点非空: cur.next 新节点 # 或接入已有节点 cur cur.next return dummy.nextdummy 的真正价值不是省一个节点而是帮你把初始化空链表这个麻烦事前置。没有哑节点的时候你要在循环里检查结果链表是不是空的代码分支复杂度翻倍有了哑节点所有节点一视同仁逻辑简单还不容易漏边界。我见过很多人写链表题函数签名没问题一提交就报错大部分都死在头节点处理上。这个模板建议直接背下来省下的时间够你多分析一道题。4. 遇到过的坑和排查技巧刷题实录4.1 为什么看答案秒懂自己写就卡壳看题解的时候觉得就这自己合上编辑器写却半天憋不出来这是刷题阶段特别普遍的问题。我自己的体会是大多数人卡壳不是因为智商而是思路没有形成稳定的输出顺序。我现在的习惯是把每一道题的解法拆成三块来看边界判断、主循环逻辑、返回值。边界判断放在最前面比如空输入、负数、长度小于 2 的情况先把最容易出错的排掉。主循环逻辑要明确循环里每一轮在干什么也就是循环不变量。返回值要清楚最终要返回什么是头节点、长度还是布尔值。拿合并两个有序链表来说边界是有一条链表为空主循环里只做一件事把较小值的节点拼接到 cur 后面返回值是 dummy.next。一旦你按这个顺序拆解写代码就变成填空题了。我建议你以后刷题时也尝试这样拆写完直接对照题解你会发现自己卡壳的位置非常固定基本就那两三个点。4.2 链表和栈题目的调试土办法调试链表题print 大法真的不用觉得丢人问题是怎么打印才有价值。我自己的习惯是在每个循环迭代里打印当前节点的值、快慢指针各自指向节点的值再把整个链表的值转成一个数组打印出来。这样一来你一眼就能看出指针移动对不对覆盖操作有没有把后面的值弄丢。栈的调试就更简单了你打印 stack 的内容和当前遍历到的字符在括号题里基本就能定位所有问题。还有一个小技巧遇到链表题拿不准先在草稿纸上把链表画成箭头图手动跑一遍小样例再写代码。画三条链的时间可能省下你半小时的反复试错。另外测试数据一定要覆盖边界比如空链表、只有一个节点、两个长度不同的链表。这些边界样例在力扣上可能不会都给你但面试时面试官一定会问。4.3 简单题的复习节奏与错题管理刷题记录不是刷完就完事了遗忘是正常现象。我自己的复习节奏是当天做错的题第二天重新做一遍第三天再看一眼第七天再做一遍第三十天再回头看一次。不需要每次都 AC但要求思路清晰、边界覆盖完整。我会在错题本里加两个标签一个是边界陷阱型比如回文数里的个位为 0 情况另一个是思路卡壳型比如我早期对栈匹配理解不到位。标签的目的不是分类而是帮你找到自己最容易犯错的方向。我统计过绝大多数人的错误集中在少数几类固定模式上找到它们性价比远高于盲目刷新题。4.4 聊聊力扣热题100简单题和它的关系现在很多新手刷题清单必提力扣热题100这确实是一份很经典的题单。但我觉得它更适合作为目标而不是起点。热题 100 里有一部分简单题比如有效的括号、爬楼梯都和我这次记录的题有直接关联还有一部分中等题、困难题本质上也是用简单题里的基础能力去解。我的建议是如果你连简单题的基础模板都没建立直接硬啃热题 100 会很受挫。我自己会先把简单题系列刷熟做到看到特征、立刻反应出该用什么数据结构再去碰热题 100效率会高很多。这个顺序不一定适合所有人但至少对我来说它把刷题这件事从痛苦追赶进度变成了逐步升级打怪。这套记录写到这儿简单题 5 算是告一段落。回头翻自己的刷题记录最大的变化不是 AC 数量变多了而是读题后的第一反应变快了看到括号想到栈看到链表头节点犯难想到哑节点看到斐波那契式递推想到滚动数组。这种条件反射靠的正是这些看似简单的题目反复打磨出来的。下一步我打算在这个系列里插入几道简单题的进阶影子题作为对照顺便聊聊怎么把简单题思路一步步延伸到中等题也希望大家在评论区留下自己最近卡住的题我挑几个典型在下篇展开。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑