面试了上百人之后,我才真正意识到,力扣(LeetCode)上那些看似基础的链表、二叉树和栈,才是区分候选人是“背题家”还是“真懂数据结构”的分水岭。很多简历上写着“熟悉常用数据结构”的同学,一问到“反转链表有几种写法,迭代和递归分别在什么场景下更优”就卡壳;而能在白板上把二叉树遍历的递归栈帧画清楚的人,往往在后续系统设计环节也表现得条理清晰。这篇文章不打算罗列题号,而是想从这三类底层数据结构的本质出发,把它们的核心考察点、经典题型的切入思路,以及我在实际刷题和面试中总结的实战经验一次说透。无论你是刚开始刷题准备实习的在校生,还是工作几年想补基础的后端开发,这篇文章都很适合你静下心读一读。
1. 内容整体设计与思路拆解
1.1 为什么面试官对这三类结构情有独钟
先回答一个很多人问过我的问题:为什么大厂面试总爱考链表、二叉树和栈,而不是直接考Redis源码或者Kafka的副本机制?答案其实很朴素——这三类结构是考察“指针操作”“递归思维”和“抽象建模”这三种底层能力的最低成本载体。
链表的指针操作直接检验你对内存模型的理解。你在纸上画一个单链表,反转它,从头到尾只有几个指针在动,但很多人就是转不过来。这不只是代码熟练度的问题,而是是否真正理解“引用”和“对象”之间的关系。我曾经带过一个实习生,他在IDE里写Java的链表反转能跑通,但我在他代码里加了一个并发修改的极端case,他立刻就懵了——因为他在用“数组思维”写链表,根本没有考虑节点之间的指向关系在并发场景下会怎样漂移。
二叉树则是递归思维的绝佳训练场。递归是很多程序员心里的一根刺,而二叉树的天然递归结构(左子树、右子树)让递归变得“不得不学”。你可以用迭代法写遍历,但如果你看不懂递归版本的代码,遇到树的动态规划(树形DP)、最近公共祖先这类题目时就会非常吃力。而栈呢,它几乎贯穿了所有“状态回溯”的场景——递归本身就是栈的调用过程,浏览器后退、编辑器Undo、括号匹配、表达式求值,全是栈的变形应用。
1.2 三类结构的内在联系与刷题优先级
从数据结构的发展脉络看,链表、二叉树和栈并不是孤立的。栈可以看作一个受限的线性表,链表是实现栈的最灵活方式之一;二叉树则可以看作“多链”的推广,每个节点有两个指针域。我的建议是刷题顺序遵循“链表 → 栈 → 二叉树”的递进关系:
- 链表帮你建立“指针/引用”的直觉,这是后面所有结构的基础;
- 栈帮你理解“线性结构的状态保存”,很多二叉树的非递归遍历都要借助栈;
- 二叉树帮你建立递归框架思维,学完之后你会发现链表的递归解法也豁然开朗。
很多攻略建议直接刷“力扣Hot 100”,我并不完全反对。但Hot 100里跳跃性太大,今天做链表明天做动态规划,基础不牢的人很容易受挫。我个人的经验是:先花一周集中把链表和栈的经典题各刷10道,再进入二叉树模块,这样过渡更平滑,而且每个模块内题目之间的相似度能帮你举一反三。
1.3 本文选用的语言与工程视角
以下所有代码示例,我会用Python来写,因为它表达数据结构的逻辑最简洁,不容易被语言细节干扰。但我会反复强调工程视角——比如栈在函数调用中的角色、链表在内存池中的应用,避免让读者停留在“为了刷题而刷题”的层面。理解了工程背景,你会发现面试官追问的场景题(比如“LRU缓存用哪种结构实现”)其实都是这些基础知识的自然延伸。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 链表核心细节解析与实操要点
2.1 链表的本质:节点与指针的舞蹈
链表,说白了就是一组节点,每个节点除了存数据,还存着下一个(甚至上一个)节点的地址。和数组相比,它的核心优势是插入和删除不需要搬移元素,只要有前驱节点指针,O(1)就能完成;缺点是随机访问是O(n),得从头一个一个找。
但链表的难点从来不在“懂定义”,而在“操作细节”:
- 边界条件:头节点为空怎么办?只有一个节点怎么办?删除的是头节点怎么办?
- 指针丢失:在中间插入节点时,如果不先保存后继节点,指针就被覆盖了。
- 环检测:如果链表有环,遍历会死循环,必须用快慢指针。
我刷题时给自己定了一条铁律:每道链表题,先画图,再写代码。纸上把节点之间箭头的每一步变化标清楚,代码基本不会出错。很多人觉得画图浪费时间,但实际上一道中等难度的链表题,画图5分钟,写代码10分钟,比直接莽写然后调试半小时高效太多。
2.2 虚拟头结点:一条解决头节点特殊性的捷径
头节点是链表操作中最容易出bug的地方。比如删除一个节点,如果是普通节点,我们需要知道它的前驱;但如果要删的是头节点,它没有前驱,必须单独处理。这种“分类讨论”写多了特别容易漏。
我的解决方案是:在真正头节点前面加一个虚拟头结点(dummy node)。这个节点不存业务数据,它的next指向真正的头。这样所有节点(包括原头节点)都有了统一的前驱,不管是插入、删除还是反转,代码逻辑可以完全一致,不用判断“是不是头节点”这个特例。
举个最经典的例子:力扣第203题“移除链表元素”。用虚拟头结点写出来的代码非常统一:
python复制def removeElements(head, val):
dummy = ListNode(-1)
dummy.next = head
cur = dummy
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummy.next
看到区别了吗?如果不用dummy,while循环里你得先判断head是否等于val,然后while循环里继续判断……代码会丑陋得多。dummy节点在链表题里的地位,就像哨兵在排序算法里的地位:用极小的空间代价,换取逻辑的极大简化。凡是涉及头节点可能被修改的题(删除、反转、合并),我第一反应就是“能不能加个dummy”。
2.3 快慢指针:链表题里的万能钥匙
如果说dummy节点解决的是“结构简化”,快慢指针解决的就是“状态搜索”。它最经典的应用有三个:
- 链表中点(力扣876):快指针走两步,慢指针走一步,快指针到终点时慢指针就在中间。
- 环检测(力扣141):快慢指针,如果相遇说明有环。
- 倒数第k个节点(力扣面试题22):快指针先走k步,然后快慢一起走,快指针到尾时慢指针就是倒数第k个。
我第一次看到“链表中点”时觉得这思路太巧妙了,但后来反思,快慢指针的本质是利用速度差把“位置关系”转化为“时间关系”。这个思想不止链表能用,在数组、字符串的一些双指针题里也同样适用。
值得提醒的是,快慢指针的代码虽然短,但循环条件很容易写错。以环检测为例:
python复制def hasCycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
注意while fast and fast.next这个条件——必须先判断fast不为空,再判断fast.next不为空,否则在链表结尾处你访问fast.next.next会直接抛空指针。这个顺序问题,我见过太多人在面试时写反了。
2.4 反转链表家族:必须刻进DNA的递归与迭代
反转链表几乎是大厂面试的高频常客,而且它有一个完整家族:反转整个链表、反转链表前N个节点、反转链表区间(力扣92)、K个一组反转链表(力扣25)。
先说最基础的“反转整个链表”。迭代法很直观:
python复制def reverseList(head):
prev = None
cur = head
while cur:
nxt = cur.next
cur.next = prev
prev = cur
cur = nxt
return prev
这里的核心技巧是:在改变cur.next之前,必须先用nxt保存cur原来的下一个节点。这个“先保存后修改”的顺序,就是链表操作中避免指针丢失的命门。
而递归法,代码更短,但理解成本更高:
python复制def reverseList(head):
if not head or not head.next:
return head
new_head = reverseList(head.next)
head.next.next = head
head.next = None
return new_head
递归的思维跳出了“迭代修改指针”的细节,直接假设“子问题已经解决”——reverseList(head.next)返回的是从head.next开始反转后的新头,此时head.next已经是原链表的尾节点了,我们只需要让head.next指向head,再让head指向None即可。
我自己的经验是:先牢牢掌握迭代法,递归法用来锻炼思维。真正面试时,迭代法出错概率更低,面试官也更喜欢看到你能讲清楚循环不变量。
3. 二叉树:递归思维的训练场
3.1 二叉树的遍历:一切题目的地基
二叉树的遍历分两种大方向:深度优先(DFS)和广度优先(BFS)。深度优先又分前序、中序、后序三种。别看遍历简单,我统计过力扣上关于二叉树的题目,至少一半都是在遍历框架上做扩展——最大深度是后序遍历边收集边计算,验证二叉搜索树是中序遍历检查有序性,层序遍历本身就是BFS。
用递归写遍历很简单,关键是把握三种序的处理时机:
- 前序:先处理当前节点,再递归左右子树 → 适合“从上往下传状态”
- 中序:先递归左子树,处理当前节点,再递归右子树 → 适合二叉搜索树(BST)场景
- 后序:先递归左右子树,再处理当前节点 → 适合“从下往上收集信息”
我用一个口诀记住它们:“根在前、根在中、根在后”。前序就是根先被访问,中序是根在中间,后序是根在最后。
给你看一个前序递归模板,几乎所有递归题都是在这个骨架上加东西:
python复制def preorder(root):
if not root:
return
# 处理当前节点,比如 print(root.val)
preorder(root.left)
preorder(root.right)
有些人觉得递归太简单反而心虚,问我“面试时写递归会不会显得太基础”。我的回答是:能写对递归本身就是一种能力,面试官在乎的是你能否讲清楚递归的终止条件和递推公式。如果非要显摆,你可以补充一种非递归写法(用显式栈模拟),跟面试官展示你连系统栈的压栈出栈过程都理解到位了。
3.2 非递归遍历:用栈模拟递归的压栈与出栈
非递归遍历能加深你对“栈”的理解,正好呼应本文的主题之一。以前序遍历为例,我们用栈来模拟系统栈的行为:
python复制def preorderTraversal(root):
res = []
stack = [root]
while stack:
node = stack.pop()
if node:
res.append(node.val)
stack.append(node.right)
stack.append(node.left)
return res
注意这里先压右子树再压左子树,因为栈是后进先出,我们想先访问左子树,就要让左子树后进栈、先弹出。这个细节很多人写错,把左右顺序搞反,结果遍历出来是“根右左”。
中序遍历的非递归写法要稍微复杂一些,因为要“先一路走到最左边”:
python复制def inorderTraversal(root):
res = []
stack = []
cur = root
while cur or stack:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
我画过很多次这个过程的图,本质上就是把递归调用时系统自动维护的栈,手动模拟了出来。理解了这个,你就理解了递归的底层运转逻辑。面试时如果被问“递归会不会导致栈溢出”,你可以自然地从这里切入,讲一讲递归深度和系统栈上限的关系。
3.3 二叉树的深度与路径问题:DFS和回溯的结合
二叉树的深度是很多人刷二叉树的第一道题,代码很短:
python复制def maxDepth(root):
if not root:
return 0
left = maxDepth(root.left)
right = maxDepth(root.right)
return max(left, right) + 1
这道题虽然简单,但它体现了后序遍历的精髓:先把左右子树的信息求出来,再综合得到当前节点的答案。我经常用这道题来检验一个人是否真的理解了递归——很多人能默写代码,但问他“这个递归的返回值是什么意思”,他就说不清了。
深度的变体题非常多:最小深度、平衡二叉树、二叉树直径、二叉树最大路径和。这些题的核心都是“在递归过程中维护一个全局变量或返回值”,关键是搞清楚你需要的答案是在“归”的过程中产生,还是在“递”的过程中传递。
路径类问题(比如力扣113“路径总和II”)则是DFS加回溯的经典场景,需要在递归进入子节点前“做选择”,递归返回后“撤销选择”:
python复制def pathSum(root, targetSum):
res = []
def dfs(node, remaining, path):
if not node:
return
path.append(node.val)
if not node.left and not node.right and remaining == node.val:
res.append(path[:]) # 深拷贝,否则后续修改会污染结果
dfs(node.left, remaining - node.val, path)
dfs(node.right, remaining - node.val, path)
path.pop() # 回溯,撤销选择
dfs(root, targetSum, [])
return res
这里的path[:]和path.pop()是黄金搭档。忘了深拷贝,最后res里所有的路径都会变成同一条;忘了回溯,path就会被污染。我见过太多人在这两个地方翻车,却以为自己逻辑有问题。
3.4 二叉搜索树与公共祖先:利用有序性解题
二叉搜索树(BST)是二叉树的重要子类型,它的核心性质是:左子树所有节点值 < 根节点值 < 右子树所有节点值,且中序遍历结果是有序的。这个性质让很多题目可以直接利用“大小关系”来剪枝,而不必遍历整棵树。
比如力扣235“二叉搜索树的最近公共祖先”,因为BST是有序的,我们可以从根节点出发:
- 如果p和q都小于当前节点,说明祖先在左子树;
- 如果p和q都大于当前节点,说明祖先在右子树;
- 否则,当前节点就是最近公共祖先。
这个思路的代码非常简洁,时间复杂度O(h),h是树的高度。而同样的题在普通二叉树中(力扣236),就得用后序遍历+回溯去做了,代码复杂度高一个量级。
我建议刷二叉树时,先把普通二叉树的套路吃透,再单独总结BST的利用有序性的特殊方法。面试官特别喜欢考BST,因为既能考察基础遍历,又能考察你是否能观察结构特性。
4. 栈:最容易被低估的数据结构
4.1 栈的本质与工程应用:函数调用与状态回溯
栈是一个只能从一端插入和删除的线性表,后进先出(LIFO)。它听起来简单,却是计算机系统中最核心的结构之一。函数调用时的参数、返回地址、局部变量都存储在“调用栈”中,递归之所以能“一层层进去再一层层出来”,靠的就是系统栈。
工程上,浏览器的后退功能、文本编辑器的Undo、括号匹配的语法检查、表达式求值、深度优先搜索(DFS)的状态管理,全部是栈的应用。所以在刷题时,我会刻意把题目和这些场景联系起来——比如做“有效的括号”这道题时,我会想到编译器在解析代码时是怎么检查括号配对的。这样刷一道题,记住的不只是代码模板,而是一类场景的解法。
4.2 单调栈:解决“下一个更大元素”类问题的利器
栈里面最值得单独拎出来讲的是单调栈。所谓单调栈,就是栈内元素保持单调递增或单调递减。它在解决“寻找下一个更大(或更小)元素”这类问题时特别高效,能把O(n^2)的暴力降为O(n)。
力扣496“下一个更大元素I”是入门题,力扣739“每日温度”是经典变体。以每日温度为例,题目要求输出每一天要等几天才能等到更高温度。暴力解法是双重循环,而单调栈的思路是:
- 遍历数组,栈中存的是下标;
- 如果当前温度比栈顶下标对应的温度高,说明栈顶这一天等到了更高温,出栈并计算结果;
- 否则当前下标入栈。
python复制def dailyTemperatures(temperatures):
n = len(temperatures)
res = [0] * n
stack = []
for i in range(n):
while stack and temperatures[i] > temperatures[stack[-1]]:
prev = stack.pop()
res[prev] = i - prev
stack.append(i)
return res
我第一次学单调栈时很困惑:“为什么要用栈?一个变量记录当前最大值不行吗?”后来才想明白:栈里存的是“还没找到答案的候选者”,它们的温度从栈底到栈顶是递减的,因为一旦出现比栈顶更高的温度,栈顶就会被弹出。 单调栈的“单调”不是刻意维持的,而是“一旦有更优值出现,旧的候选项自然被淘汰”的必然结果。
这里顺便提一下,热搜词里出现了“粒子群算法原理”“音频重采样算法”这些看起来很不相关的词。我的看法是,这类算法题和数据结构题并不冲突——刷数据结构题训练的是“建模能力”,刷算法题(比如DP、贪心、KMP)训练的是“状态转移思维”。两者都是面试考察的一部分,但数据结构是地基,建议先把地基打牢再上难度。
4.3 栈与递归、表达式求值:手动模拟系统行为
另一个高频考点是用栈实现队列、用队列实现栈(力扣232、225),以及表达式求值(中缀转后缀、后缀表达式求值)。做这些题的时候,不用急着写代码,先想清楚一件事:栈和队列的行为差异到底在哪里?
栈是LIFO,队列是FIFO。用栈模拟队列,就需要“负负得正”——用两个栈,一个负责入队,一个负责出队;出队时把入队栈的元素全部倒到出队栈,顺序就反转了。这个“双栈倒腾”的思路,我在很多系统设计中都用过,比如消息的顺序处理、流式数据的时间窗口反转。
表达式求值稍微复杂些,但核心也是栈:操作数入操作数栈,运算符入运算符栈,遇到右括号时弹出括号内的表达式计算。这个过程的本质是用栈保存“暂时不能计算”的部分。你可以把它想象成做菜:有些食材(操作数)要先放着,有些调料(运算符)要等合适的时机才能加进去,栈就是你手边的操作台。
5. 实战方法论:力扣刷题顺序与高效复现
5.1 热题100的正确打开方式
“力扣热题100”是很多人刷题的第一站,但我的观察是,很多人刷热题是“刷了个寂寞”——看完题解,代码敲一遍,AC了,下一题。一周后再问,啥也不记得。
我的建议是:把热题100当作“题型的索引”,而不是“题库本身”。具体操作是:
- 先用本文介绍的链表、栈、二叉树模块分类,把热题100里属于这三类的题挑出来(大概有40道左右);
- 按“同类型题集中刷”的原则,每类题型连续刷3到5道,直到形成肌肉记忆;
- 刷完一遍之后,第二遍直接按专题刷,不再按顺序。
我个人的经验数据是:热题100刷两遍的效果,远好于刷500道题各一遍。刷题的质量不在于数量,而在于你能否在见到新题时,识别出它其实是某个旧题型的变体。
5.2 三轮刷题法:从“看题解”到“默写”再到“变形”
第一轮刷题时,遇到不会的题看题解,这很正常,不要有心理负担。但看完题解一定要做两件事:第一,把题解的思路用自己的话写一遍;第二,关掉题解,自己从头写一遍代码。这两步做完,这道题才算真正过了一遍。
第二轮刷题时,要模拟面试节奏——拿到题,先跟“看不见的面试官”讲思路(可以自言自语或者写注释),再用白板或者编辑器写代码,全程控制在20到30分钟内。这个阶段的目标是:旧题能默写,新题能套框架。
第三轮刷题时,就要做变形了。怎么变形?看讨论区里别人提的follow-up,或者自己问自己:“如果我把链表改成双向链表会怎样?”“如果二叉树节点有指向父节点的指针,这题会简单多少?”这个阶段训练的是“举一反三”的能力,也是面试时让面试官眼前一亮的关键。
5.3 时间分配与复习节奏
我建议每天保持1到2小时的刷题时间,周末可以加量到3到4小时。分配上,每两周滚动一个专题:第一周链表,第二周栈,第三四周二叉树。每天刷3道题左右,其中1道是新题,2道是前一天的旧题重做。
这里面最容易被忽视的是“旧题重做”。很多人刷题贪多,但艾宾浩斯遗忘曲线告诉我们,不复习的话,一周后你大概只能记住20%的内容。我刚刷题那会儿也不信邪,后来发现一周前觉得“这题太简单了”的题,一周后竟然要重新看题解才能写出来。从那以后,我把“复习旧题”当作每天必须完成的任务,优先级高于“做新题”。
5.4 面试中的表现技巧:从“会做题”到“会讲题”
面试和刷题不太一样。刷题时你只需要把代码写对,面试时你还需要把思路讲清楚。我总结了三个实用技巧:
- 先确认题意,再动手:用自己的话复述一遍题目要求,顺带问清边界条件(比如“链表为空时返回什么?”“数组长度可能为0吗?”)。这既避免理解偏差,又展示沟通能力。
- 边写边说思路:写代码时同步说出代码的宏观逻辑,比如“这里用快慢指针,快指针走两步慢指针走一步,这样能在不借助额外空间的情况下找到中点”。让面试官跟上你的思路,比闷头写代码效果好得多。
- 写完后主动测试:不要等面试官问,自己先举一个普通case、一个边界case、一个空case来跑一遍逻辑。这个习惯在面试中非常加分,因为大部分人都会忽略边界测试。
6. 常见问题与排查技巧实录
6.1 链表操作的三个高频翻车现场
链表题翻车点其实非常集中,我总结下来,90%的错误逃不出这三类:
- 空指针访问:比如在
cur.next.next之前没确认cur.next是否存在。这种问题最好的解决办法就是写代码前先画图,画完图再看一遍代码,逐个指针追一遍。 - 修改指针后丢失后继:前面反复强调过,在修改
cur.next之前,必须把cur.next的引用保存下来。我在初学阶段吃过很多次亏,之后养成一个习惯:链表题里只要是“赋值”操作,先问自己“这根指针原来的值还用不用?不用了再覆盖。” - 死循环:常见于反转链表或环形链表处理时,某个节点的next指向了自己。遇到代码跑起来没结果、或者输出一个循环列表,优先检查是不是某个节点的next被错误地指回到了它前面的节点。
举个例子,我见过很多人在写反转链表时,忘了把原头节点的next置为None,然后打印的时候整个链表循环输出,一直打印不完。排查方法也很简单:用一个计数器限制循环次数,先定位死循环是不是真的存在,再看具体是哪个节点指回了哪里。
6.2 二叉树递归的栈溢出与重复计算
二叉树递归最常见的两个问题是:递归深度过大导致栈溢出,以及重复计算导致超时。栈溢出一般出在“链表状的二叉树”上(比如每个节点只有左孩子),这种情况下递归深度等于节点数,10000个节点可能就把系统栈压爆了。解决思路有两个:一是改成迭代加显式栈,二是让递归函数“尾递归”化(但Python等语言不一定能优化)。
重复计算的一个经典例子是“计算二叉树的最大深度”如果用暴力方式,会重复遍历子节点。更典型的场景是动态规划类的树形DP,比如“打家劫舍III”,如果不加记忆化,复杂度会爆炸。我排查重复计算的方法是:在递归函数入口加一行打印或计数,看同一个节点被访问了多少次。 如果远超预期,就考虑增加缓存(字典或数组)来记录已计算过的状态。
6.3 栈相关题目的调试心得
栈题目的调试比链表和二叉树要容易一些,因为栈结构很直观。但我还是遇到过一些有意思的bug:
- 最经典的错误是“pop前忘了判断栈是否为空”。尤其是在做括号匹配时,如果字符串以右括号开头,栈为空时pop会直接报错。正确的做法是:如果当前是右括号且栈为空,直接返回False。
- 其次是“单调栈里存下标还是存值”的问题。如果你需要知道“距离”,那就必须存下标;如果只需要知道值,存值就可以了。很多人在“每日温度”这类题里存了值,结果算距离时无从下手,只能傻眼。
- 最后是“用栈模拟递归时,压栈顺序和递归实际执行顺序不一致”的问题。这个没有捷径,只能手动模拟一遍过程,把每一步栈的变化画出来。我当年学二叉树非递归遍历时,画了整整三张A4纸的栈状态图,从那以后再也没有错过压栈顺序。
6.4 速查表:三类数据结构的常用技巧与力扣经典题
为了让你后面复习方便,我整理一个简洁的速查表,把每个结构最核心的技巧和对应的题目类型列出来:
| 数据结构 | 核心技巧 | 高频题型 | 推荐题号(力扣) |
|---|---|---|---|
| 链表 | 虚拟头结点、快慢指针、先保存后修改 | 反转、删除、合并、环检测 | 206、203、21、141、876、25、92 |
| 二叉树 | 递归遍历框架、前中后序处理时机、回溯 | 深度、路径、最近公共祖先、BST | 104、102、113、236、235、98、105 |
| 栈 | 后进先出模拟状态、单调栈、双栈倒腾 | 括号匹配、表达式求值、下一个更大元素 | 20、232、225、496、739、150 |
这个表不是让你去背题号,而是给你一个“题型索引”。我自己的习惯是,每隔一段时间就对着这张表自测一下,看哪个技巧已经忘了,就回去把那类题的经典题重做一遍。定期回归基础,比持续刷新题更重要。
还有一个小技巧:刷题时把每道题的“暴力解”写一遍。不要一上来就想最优解。暴力解能帮你验证自己对题意的理解是否正确,也能让你在跟面试官聊思路时,先给一个“保底方案”,再逐步优化。很多面试官其实更看重这种“从暴力到最优”的思考过程,而不是直接写出最优解。
最后再分享一个我一直在用的习惯:每道题AC之后,我会去讨论区看两到三种其他解法,尤其是那些国内外的热门解答。不是为了多记几个代码模板,而是为了理解同一个问题还能从哪些角度切入。刷题到后期,真正拉开差距的不是代码能力,而是思路的广度。你能想到几种解法,决定了你在面试中能不能和面试官“聊起来”,而不只是“写出来”。
