算法面试高频题型梳理与刷题复盘方法论

发布时间:2026/10/13 7:21:36
算法面试高频题型梳理与刷题复盘方法论
经常有人问我算法题到底怎么刷才有效网上题库动辄几千题今天刷一道明天刷一道最后面试该不会还是不会。我自己也经历过那个阶段后来索性把所有碰过的题目重新做了一遍梳理按照题型、难度、考点重新归档就成了这个叫“部分算法题”的项目。它不是那种全量题库搬运而是把最高频、最核心、最容易在面试里考到的题型抽出来配上解题思路和模板代码。这篇文章我就好好讲讲这个项目是怎么设计的、里面那些题到底该怎么拆、怎么做才真正消化掉以及我自己实测下来踩过的一堆坑。适合谁看如果你正准备技术面试、刚开始刷题找不到方向、或者刷到几千题但在手写代码时还是卡壳这份梳理的思路应该能给你一个可复用的框架。我尽量少讲虚的多讲怎么落地毕竟算法这玩意儿最终看的还是你能不能在白板上写出能跑的代码。1. 内容整体设计与思路拆解1.1 为什么只保留“部分”题而不是全盘照搬市面上算法题总量太大其中大量题目要么是数学脑筋急转弯要么是各大题库为了凑数量堆出来的变体。真要全刷一遍这辈子基本就交代在这上面了。所以这个项目一开始就定了个原则只保留“值得反复做三遍以上”的题目。什么叫“值得做三遍”我的判断标准有三个第一这道题背后代表了一种独立的解题范式比如二分答案、单调栈、状态压缩、树上差分做过一次它能辐射一整类题第二它是面试里出现频次极高的题型比如链表反转、LRU缓存、最长回文子串这一类第三这道题踩坑价值高也就是它能在某个细节上把你虐到怀疑人生做完之后能让你对某个知识点有更深的理解。按这个标准筛选下来最终留下的题大概覆盖了十几个核心考点区域。比起按数量堆积的题库这种筛选更像是“最小可复现知识集”每一道题都是一个锚点面试时遇到新题先在脑子里匹配锚点类型再套范式最后填细节。1.2 按“面试考查权重”设计归档维度刷题不能按题号顺序刷那样太被动了。这个项目的归档方式是按两个维度交叉划分的第一维度是数据结构数组、链表、栈与队列、哈希表、树、图、堆、并查集第二维度是算法思想二分、递归回溯、深度优先搜索、广度优先搜索、贪心、动态规划、分治、滑动窗口、双指针。这两个维度交叉之后就形成了一张网格每道题都能落到网格的某个格子里。面试官出的题再怎么变化也逃不出这张网格的范围。你在搜索一道算法题时先别急着看题解先自己想它属于哪个格子。这样做的好处是长期下来你在大脑里建立了一张“题型地图”而不是零散的一堆孤立解法。1.3 应对面试场景的特殊设计考量说实话面试算法题和平时做练习题有本质区别。平时做题你可以翻书、打断调试、搜资料但面试现场白板写码拼的是“短时间内快速定位解法”和“手写代码一次写对”的能力。所以这个项目在归档时额外标注了几项面试针对性指标第一是高概率高频题这类题必须做到肌肉记忆级别也就是闭着眼睛能把模板敲出来第二是低概率冷门题这类题了解原理即可不必追求一次通过第三是临场易错题专门汇总那种思路对但代码老写错边界的题第四是思路讲解复杂度用来判断一道题适合用几分钟向面试官讲清思路。我后来带过几个同学模拟面试发现很多人在白板上卡住的根本原因不是不会算法而是对题型的定位太慢。有的题一看就是典型动态规划他先犹豫了五分钟“这题是不是可以用双指针”说明他脑内的题型地图不够清晰。这个项目里专门设置了“一眼定题型”的练习环节用的就是这些归档好的题目。2. 高频题型分类与典型题目拆解2.1 数组与双指针最简单的容器最深的坑数组这块我建议从双指针入手它既能解决很多问题又不像动态规划那样一上来就把人劝退。但数组看似简单实际坑非常多尤其是边界条件。这个项目里归档的典型题包括有序数组去重、盛最多水的容器、三数之和、接雨水等。以三数之和为例它的核心思路是先排序然后固定一个数再用双指针去扫剩余区间。但不看题解自己写一遍的话十个人有八个会栽在“去重”这一步去重不能只针对外层固定的数内层双指针移动时也要去重否则结果里会出现相同的三元组。很多解法讲的时候说得轻巧等到手写代码的时候就在这个细节上露馅。再比如接雨水这道题暴力能过但这题白做了。它真正的价值在于让你理解“按行算”和“按列算”的区别然后自然引出单调栈解法。这类题我的建议是做完之后一定要把复杂度重新分析一遍并且能用不同方法各自实现一次才算是真正吃透了。经验之谈数组题的边界条件建议养成一个习惯在写代码之前先用一句话说清楚区间定义比如“左闭右开”还是“左闭右闭”。很多边界错误都是因为前后区间定义不一致导致的。2.2 链表递归和迭代的那个经典交接点链表题在面试里属于“大家都觉得自己会但一写就乱”的类型。这个项目里归档了链表反转、合并两个有序链表、环形链表检测、链表排序、K个一组翻转链表等经典题。链表反转是一个所有解法的基础但很多人以为背个迭代代码就完事儿了。实际上这道题真正要掌握的是“三种形态的统一理解”迭代反转、递归反转、头插法反转。很多人面试时被问到“你用递归实现一下”当场卡住就是因为他只会背迭代没理解递归反转里每一步到底在返回什么。K个一组翻转链表更是高频它考察的对链表操作的熟练度非常高。难点不在于翻转本身而在于“前一组和后一组之间怎么接起来”。我的做法是先把大问题拆成两个子问题写一个翻转区间内的函数再在链表上循环调用它。只要这两个子问题各自清晰整体思路就顺了。2.3 二叉树与递归别把递归想复杂了树和递归是密不可分的因为树结构的定义本身就是递归的。这部分归档了前序中序后序遍历、二叉树的最大深度、验证二叉搜索树、最近公共祖先、二叉树的层序遍历、路径总和系列等题目。我给这类题目定了一个心法不要试图在脑子里模拟完整的递归栈而是只关注“当前节点要做什么然后相信递归”。比如翻转二叉树很多人第一次做的时候试图把整个递归过程想清楚反而把自己绕晕了。正确的做法是如果当前节点是空返回空否则交换当前节点的左右子树然后递归处理左右子树。至于底层怎么一层层翻转上来的交给递归不用操心。验证二叉搜索树这道题是个经典陷阱题看起来很简单用中序遍历是否有序就能解。但如果题目要求你直接用递归判断每个节点和左右子树的关系很多人就会写上“只检查当前节点是否大于左子、小于右子”这种有漏洞的代码。正确做法是把每个节点的取值范围作为参数传下去而不是只和直接子节点比较。注意二叉树相关的递归代码写完之后一定要检查“空指针”这个分支。我见过太多人的树类题死在不处理空节点上。2.4 动态规划最难啃但也最值得啃的硬骨头动态规划是整个算法面试里区分度的最大来源。很多同学第一步就卡在“到底怎么判断一道题能用动态规划解”上。判断标准其实很朴素第一问题能分成互相独立的子问题第二子问题之间有重叠第三存在一种状态定义能把子问题的答案存下来。这部分归档的题目按递进关系排列爬楼梯、斐波那契数列这种入门题不同路径、最小路径和这类二维网格题打家劫舍、买卖股票这类序列决策题最后是背包问题、编辑距离这类需要更复杂状态压缩的题。以编辑距离为例它可以说是动态规划里最典型的二维状态转移题。状态定义是“A的前i个字符变成B的前j个字符需要的最少操作数”转移方程看着简单但真正理解它需要你自己推一遍为什么增加一个字符是dp[i-1][j]1、删除是dp[i][j-1]1、替换是dp[i-1][j-1]cost。这些不是背公式就能内化的必须动手模拟一个小例子。动态规划里还有一类很常见的问题叫“状态压缩”。比如二维状态只依赖上一行数据就可以把二维数组压成一维数组。这在你后续处理大量数据时会经常用到但面试高频考点里也常出现。平时做练习时我会刻意要求自己对能压缩的状态都试着压缩一遍空间复杂度一下就从O(n * m)降到了O(m)。2.5 图论与搜索BFS、DFS、拓扑排序和并查集图论这个模块在面试里的位置有点微妙真正考到图的频率不如数组、树、动态规划那么高但一旦考到就是送命题。这部分归档了岛屿问题基本是DFS单词接龙典型BFS课程表拓扑排序并查集相关题目。岛屿类问题是最能体现DFS/BFS套路感的题目。核心模式就三步遍历所有格子遇到未访问的岛屿就把整个岛屿标记掉数量加一。这里有个常见误区是不知道用什么数据结构记录“是否访问过”。有的题可以原地修改矩阵值但面试时最好先问清楚能否修改输入不行就用一个额外的visited数组避免被面试官追问。BFS还有一个非常重要的变体叫“多源BFS”比如腐烂的橘子这道题。它的核心思想是找到给定的下一个时间点多个源头同时向外扩散。很多人在写这类代码时没有先把所有源头一次性入队而是一个个处理导致扩散过程不是同步的结果完全错误。并查集题目在国内面试中出现频率越来越高因为它能干净利落地解决“动态连通性”问题。并查集的模板代码其实很短但要在面试现场写对必须理解路径压缩和按秩合并这两处优化是干什么的前者让树变矮后者防止树退化成链。2.6 回溯与排列组合暴力搜索的艺术回溯是另一种高频考点它的本质就是DFS加上撤销操作。归档的典型题有全排列、子集、组合总和、N皇后、括号生成等。这类题的精髓是“递归之前做选择递归之后撤销选择”。我见过太多人把回溯题写成了无限递归根本原因就是没有想清楚base case是什么。比如全排列你的递归终止条件是当前排列长度等于总长度那递归里的循环就只是为了穷举下一个位置放哪个数。如果不把这两个层次分开思考代码就会乱。组合总和和N皇后这类题还涉及剪枝优化。剪枝并不是花哨的高级操作而是一种让递归少走冤枉路的朴素思想。比如组合总和里如果你把数组先排序当当前数大于剩余目标值时就可以直接break而不是继续循环。这个优化在数据量大时能省下几倍的运行时间。注意回溯模板很简单但“怎么剪枝”“怎么去重”才是区分代码好坏的关键。写完题目之后建议再想想如果数据规模扩大100倍这段代码还能跑吗如果跑不了问题出在哪里3. 核心模板与代码示例3.1 手写一个滑动窗口的通用模板滑动窗口是数组和字符串题型里非常实用的技巧这里我分享一下这个项目里反复使用的一个模板它是应对“子数组/子串”类问题的重要基础工具。def sliding_window(s): n len(s) window {} left 0 # 用于记录满足条件的窗口结果 ans 0 for right in range(n): # 将 s[right] 加入窗口 window[s[right]] window.get(s[right], 0) 1 # 当窗口不满足题目条件时收缩 left while 窗口不满足条件: # 移除 s[left] window[s[left]] - 1 if window[s[left]] 0: del window[s[left]] left 1 # 此时窗口一定满足条件更新答案 ans max(ans, right - left 1) return ans这个模板的通用性很强因为它把“条件判断”和“答案更新”的代码都留白你自己去填。关键点是理解右指针负责扩展左指针负责收缩窗口的合法状态始终由条件驱动。真正写代码时最容易错的是右侧新加入元素后窗口条件被破坏却忘记收缩还有一类是窗口需要记录两种以上信息时条件判断写不清楚。建议多拿“无重复字符的最长子串”和“最小覆盖子串”这两道题来练熟这个模板因为它们一个靠哈希计数判断重复一个靠计数判断覆盖正好覆盖了两种常见场景。3.2 回溯算法的标准骨架回溯也是面试里经常要手写模板的题型我推荐把下面这个骨架背到肌肉记忆的程度def backtrack(path, choices): if 满足结束条件: 记录结果 return for choice in choices: 做选择 backtrack(新的path, 新的choices) 撤销选择下面以全排列为例看它怎么落地def permute(nums): result [] n len(nums) used [False] * n path [] def backtrack(): if len(path) n: result.append(path[:]) return for i in range(n): if used[i]: continue used[i] True path.append(nums[i]) backtrack() path.pop() used[i] False backtrack() return result核心习惯有两个一是记录结果时必须用path[:]拷贝一份而不是直接append(path)否则最终存进去的全是同一个对象的引用二是剪枝逻辑要写在“做选择”之前顺序不能错。回溯题做完之后可以试着在草稿纸上画出递归调用的状态树这能帮你确认剪枝是否正确。3.3 链表的递归反转模板链表反转用递归解法能很自然地表达“递归是自底向上构建”的过程。这里给出参考模板def reverseList(head): if head is None or head.next is None: return head new_head reverseList(head.next) head.next.next head head.next None return new_head用生活化类比来理解假设你有一列火车递归到最后一节车厢后开始往回走每走一节就把这节车厢掉头接到前一节后面最后新的火车头就是原来的车尾。这个模板看着简单但很多人第一次写会忘记把当前节点的next置空导致链表里出现环。我当初也是被这个bug坑了整整一个晚上。4. 常见问题与排查技巧实录4.1 递归“无限循环”和栈溢出很多同学的递归代码在本地一跑就Stack Overflow第一反应是“我的递归终止条件写错了”但排查下来往往发现终止条件没错错的是递归参数没有朝终止条件收敛。比如二叉树的深度计算如果每次传入的节点写错了对象就会在同样两个节点之间来回跳。排查这类问题我的建议是别上来就在本地大输入上跑先加一个打印深度看看递归调用栈最高到多少层就直接崩溃。一般来说递归过深不一定是逻辑错了也可能是递推公式设计得不合适导致状态空间太大。这时要回头审视自己是不是把多余状态也放进去了。另外Python等语言有递归层数限制如果你深层递归一跑就红字报错可以考虑自己加一个参数记录深度这样即使不报错你也知道自己递归到了多少层。提示排查任何老出错的递归题先在纸上手工跑一遍输入规模为2或3的用例保证逻辑没问题再上机器能省一半以上的调试时间。4.2 二维数组索引越界怎么快速定位做岛屿类、网格类DFS/BFS题目时最常见的报错就是IndexError。这类错误很多情况下不是单个坐标算错而是你在“访问邻居”的时候越界。项目里我总结了专门用于防越界的写法核心思路是先判断坐标是否在矩阵范围内再决定是否访问。directions [(0, 1), (0, -1), (1, 0), (-1, 0)] for dx, dy in directions: nx, ny x dx, y dy if 0 nx rows and 0 ny cols: # 访问 (nx, ny)凡是二维网格题我一律建议把所有邻居扩张逻辑集中在一个地方不要在每个递归分支里单独写越界判断这样不仅代码整洁排查问题也方便不少。4.3 动态规划的状态初始化错误动态规划里最常见的Bug不是转移方程写错而是初始化写错。拿二维DP来说很多人能写出状态转移但第一行和第一列的初始值拍脑袋就填了导致整个表错了一半。最典型的就是求最小路径和时网格第一行只能从左过来第一列只能从上过来这两个方向必须单独初始化不能用统一的转移逻辑。还有一种情况是把“非法状态”初始化为0但0在某些题目里是合法的答案导致状态覆盖出错误。比如求最长递增子序列长度时dp数组的非法初始态就应该是1而不是0因为单个元素至少能构成一个长度为1的子序列。初始化之前建议先问自己这个状态的“最小有效值”是多少它和“无效值”怎么区分4.4 写完代码老超时复杂度分析不能只靠感觉很多人的代码在力扣上跑不过全部用例一看就是时间复杂度太高。但你说他也不知道该优化哪里因为“感觉每个循环都是必须的”。我比较推荐一个习惯每道题解完之后强制自己用大O符号写出时间复杂度和空间复杂度然后对照数据范围检查是否合理。这个习惯特别有用。比如一道题的数据范围是10的5次方那O(n²)的解法基本就是超时预定此时就要考虑怎么优化到O(n log n)或者O(n)。实时监控自己的复杂度层级比刷题数量本身更重要因为面试官在白板环节一定会追问“你的算法复杂度是多少”你必须能回答得出。4.5 手写代码时变量命名混乱导致低级Bug新手写算法题时还有一类非常隐蔽的问题就是变量命名混乱。比如left、l、low、lo在同一个函数里混用一旦逻辑变复杂自己都搞不清哪个变量维护的是哪个区间。我的建议是即使算法题这种短代码也尽量用语义化的命名比如window_left、window_right、node、curr、prev。变量名太短不一定会导致答案错误但一定会导致你排查时间变长面试时看起来特别不专业。我在模拟面试中见过不少候选人解题思路完全正确但指着代码里的tmp半天说不清它代表什么这种印象分损失其实很大。5. 刷题工作流与长期复盘建议5.1 我用什么工具管理这些算法题我自己维护这种算法题合集的时候工具选得不复杂但非常顺手。第一是VS Code配合一个LeetCode插件可以直接在本地拉题、写代码、跑测试第二是一个简单的本地文件夹结构按数据结构和算法思想分类存放题解第三是一个Markdown笔记文件记录每道题的“坑点”和“复盘提醒”。这样的工具组合比直接依赖在线题库的收藏夹更有掌控感因为本地资料能长期积累不会随着账号失效或被平台改版而丢失。有一点要特别建议每道题解之后写一段两到三行的简短总结包含题型归类、核心思路、复杂度以及“我第一次做错的地方”。这些总结才是你复盘的宝贵资料而不是那堆已经有的代码。5.2 如何复盘才能避免“刷了就忘”很多人刷了几百道题之后面对新题依然没思路最大的问题在于复盘方式不对只是机械地“看题-写题-对答案-下一题”大脑里根本没有建立知识索引。这里分享一个我自己坚持了很久的方法每周挑三到四道之前做过的题不参考任何材料从零开始重新实现一遍。请注意这里的复现不是默写代码而是要完整重现自己的思考过程这题属于什么类型为什么用这个数据结构状态怎么定义转移方程怎么推导边界条件有哪些如果直接能顺利复现这道题才算真正内化。如果卡壳了就去看自己的复盘笔记找找当初卡在哪儿而不是重新看题解。5.3 面试前一周的冲刺策略面试前一周不适合再去碰新题而是要切换到“实战演练模式”。我的做法是模拟面试环境拉一个随机标签的题目在白板上用笔和纸或者Markdown编辑器手写代码限定在30分钟内完成。这个过程重点锻炼的是“时间分配能力”前5分钟必须把思路想清楚并想好复杂度中间20分钟写代码最后5分钟自查边界。如果你手头这个“部分算法题”项目已经整理好了高频题和易错题面试前几天就只看这两类反复看自己总结的复盘笔记就够了。不用临阵磨枪去刷新题那样反而会打乱已经建立起来的题型地图和信心。5.4 这个项目后续还能怎么扩展“部分算法题”这个项目做完了并不代表结束它是个活的项目。你可以持续把平时工作中遇到的有意思的算法题加进对应分类也可以按题目难度给每道题打上难度标签形成一条从入门到进阶的升级路线。更进一步可以为每道题补充“一题多解”的对比解析把多种解法的复杂度、编码难度、面试表现放在一起对比。我个人实测下来做完这些扩展之后我再面对任何一道新的算法题时每隔几秒就能在脑内定位它的题型归属然后快速套出对应范式。这种感觉遥想一下就像你在一个乱糟糟的工具箱里翻东西后来把每个工具挂了标签、放了固定位置拿什么都是顺手的事。最后再补充一句算法刷题是一条需要耐住寂寞的长路技巧可以学模板可以背但真正拉开差距的还是你对自己的思考过程有没有足够细致和诚实的复盘。这个项目承载的正是我对自己这些思考的沉淀也希望它对你的刷题方式能有点启发。