简介这是一份面向技术面试和高频算法考察的总结性资料整合了《剑指 offer》、LeetCode、LintCode 等主流题源中的典型问题适合有基础、正在准备校招或跳槽的开发者集中突破。资源仅打包为 1 个 PDF 文件大小 3.36MB结构紧凑方便在电脑或手机上随时翻阅。正文按数组、字符串、链表、树、栈和队列、数学、图、设计、海量数据、C/C 基础等模块展开目录清晰从数组中重复的数字、旋转数组的最小数字、连续子数组的最大和到正则表达式匹配、最长公共子序列、二维数组中的查找等高频考点均有涉及。每道题给出题目来源和核心思路既能用于快速过一遍常见题型也能作为面试前的查漏补缺手册。目前已有 175 人学习尤其适合需要系统梳理算法体系、提升解题效率的开发者。1. 算法题总结 274把刷过的题变成能复用的模式库如果你手上有一份叫「算法题总结 274」的 PDF不管它是从前辈那里拷来的题单还是自己花了几个月整理的笔记真正决定它价值的不是 274 这个数字而是里面每道题有没有被拆解成可复用的模式。刷过几百道题的人常有这种体验题量上去了碰到新题还是不知道从哪下手而有些人只刷了一百多道却能在看到题目的前三十秒就锁定解法方向。差别不在记性在总结方式。最常见的错误是把总结做成题解合集抄一遍代码、贴一段官方分析然后就没有然后了。一份合格的算法题总结本质上是一张模式检索表——看到「有序数组 查找」就想到二分查找算法看到「子串匹配」就立刻画出 KMP 的 next 数组看到「图上求最短路径」先判断是单源还是多源再决定是 Dijkstra 还是多源 BFS。本文以「算法题总结 274」这类题单为起点讲怎么把散题整理成自己的模式库先立分类框架再沉淀高频算法模板接着用一道题演示总结条目的写法最后解决「总结完怎么复习」的问题。适合正在准备算法面试的工程师也适合带竞赛、做内部技术分享的人参考。2. 搭算法总结的框架按数据结构与算法范式归档而不是按题号归档2.1 为什么按题号归档的题单留不下东西多数 PDF 题单的目录是按题号排的第 1 题到第 274 题依次排列。这种顺序对「从头做到尾」的刷题模式友好但对复习极不友好你记得第 148 题是道贪心算法题但目录不会告诉你第 148、第 53、第 201 题其实是同一个套路。等刷到后半程前面总结过的东西早就忘了回翻又找不到对应的章节。我一般会把总结文档的目录推翻重来按「数据结构」和「算法范式」两个维度做标签而不是按题目编号组织。这样做的理由很直接面试和竞赛考的是模式识别能力题目是模式的外壳外壳可以千变万化核心的状态转移、指针移动、堆的贪心选择是不变的。把同类题放在一起才能看出规律把不同类的题混在一起只能复习到「我做过这道题」的错觉。2.2 一套可落地的六段式分类骨架下面这张表是我整理算法题总结时用的分类骨架覆盖了绝大多数笔试和面试场景。你不必照搬但建议至少包含「线性结构」「树与图」「动态规划」「经典算法范式」这四块因为它们对应着面试官最常出题的方向。大类子类典型题目特征需要记忆的核心结论线性结构数组、链表、栈、队列、哈希表涉及遍历、双指针、单调栈、滑动窗口滑动窗口的扩展与收缩条件写在 while 里树与图二叉树、二叉搜索树、图、拓扑树的遍历、最近公共祖先、最短路、连通分量树的递归返回值语义必须先定义清楚动态规划线性 DP、区间 DP、背包、状态压缩存在重叠子问题和最优子结构dp 数组的下标含义是第一注释经典范式排序、二分、贪心、回溯、剪枝有序数组查找、区间调度、排列组合枚举贪心需要证明回溯需要画递归树字符串KMP、Trie、Manacher、滚动哈希子串匹配、前缀查询、回文串next 数组是 KMP 的唯一难点数学与杂项位运算、素数、快速幂、并查集与二进制、等价类、模运算相关并查集路径压缩必须配合按秩合并做分类的时候有一个原则一道题可以挂多个标签但必须在其中一个标签下作为「主条目」存放完整总结其他标签下只放一行引用。否则会出现同一道题在三个分类里各写一遍改一处漏两处的情况。2.3 用 Markdown 给每道题建一个固定结构的卡片建立目录之后每一道题都要有固定格式的卡片。我用 Markdown 写因为可以放到 Git 仓库里做版本管理也可以导出成 PDF 和 HTML。每个卡片包含七个字段题目名与来源、难度、模式标签、一句话思路、复杂度、代码、踩坑记录。## 题目跳跃游戏 II - 标签贪心算法 / 数组 / BFS思想 - 难度中等 - 一句话思路维护当前步能到达的最远位置以及下一步能到达的最远位置 - 时间复杂度O(n) - 空间复杂度O(1) ### 代码Python def jump(nums): n len(nums) # cur_end 是当前步的右边界next_end 是下一步能到的最远位置 cur_end, next_end, steps 0, 0, 0 for i in range(n - 1): next_end max(next_end, i nums[i]) if i cur_end: cur_end next_end steps 1 return steps字段说明标签字段决定这道题在总目录里出现在哪几个小节建议用「范式 数据结构」组合比如「贪心算法 数组」一句话思路必须是你自己重新组织过的语言不能直接抄题解复杂度写在代码之前方便复习时先判断是否可能满足面试官要求。踩坑记录单独留一行写「为什么这次没做出来」或「上次把边界条件写错在哪」。提示写「一句话思路」时有个检验标准——如果三个月后的你能靠这句话把代码默写出来说明它合格如果还需要看代码才能想起来说明这句话写的是答案而不是思路。3. 高频算法模板总结里必须有的代码、边界与参数3.1 二分查找模板统一左闭右开写法二分查找算法是面试中出现频率最高的基础算法之一但也是边界条件出错率最高的地方。常见的错误是while (left right)和while (left right)混用导致退出时left和right的关系不清晰。我建议在总结里只保留一种写法左闭右开区间[left, right)把 mid 的计算和收缩条件固定下来。# 在有序数组 nums 中查找 target返回下标不存在则返回 -1 def binary_search(nums, target): left, right 0, len(nums) # right 是开区间边界 while left right: mid left (right - left) // 2 # 防止整数溢出 if nums[mid] target: left mid 1 # target 在右半区mid 已经排除 else: right mid # target 在左半区mid 可能命中 # 退出循环时 left right只需验证这一个位置 return left if left len(nums) and nums[left] target else -1为什么统一用左闭右开区间[left, right)的不变量是「左闭右开」所以当nums[mid] target时mid及其左边都必然小于 targetleft mid 1是安全的当nums[mid] target时mid可能是答案right mid保留了 mid。循环退出时left right答案只可能在这个位置省去同时考虑两个边界的麻烦。mid left (right - left) // 2是为了避免left right在极端情况下溢出这在 C 和 Java 里是必须写的防御式写法。3.2 KMP 的 next 数组错误匹配时回退的位置表字符串匹配是算法题总结 274 这类题单里绕不开的题型。KMP 算法的核心不是匹配过程而是 next 数组的构建——它是 KMP 与暴力匹配的唯一区别。next[i] 表示pattern[0:i]不含 i这个前缀中最长的相等真前后缀长度。构建 next 数组的代码要注意i是当前要计算的位置j是已经匹配的前缀长度。// 构建 KMP 的 next 数组pattern 是模式串 static int[] buildNext(String pattern) { int m pattern.length(); int[] next new int[m]; next[0] 0; // 长度为 1 的前缀没有真前后缀 int j 0; // j 表示已匹配的长度也是下一次要比较的位置 for (int i 1; i m; i) { // 失配时回退直到匹配或回退到开头 while (j 0 pattern.charAt(i) ! pattern.charAt(j)) { j next[j - 1]; } if (pattern.charAt(i) pattern.charAt(j)) { j; } next[i] j; } return next; }这段代码里最容易迷惑的是while (j 0 pattern.charAt(i) ! pattern.charAt(j))这一行。循环退出后有两种可能j回退到 0说明前面没有可以利用的相等前后缀或者pattern.charAt(i) pattern.charAt(j)说明找到了一个更短的相等前后缀。特别注意j next[j - 1]而不是j--因为回退的目标是「当前已匹配前缀的次长相等前后缀」而不是简单往前挪一位。这是 KMP 从暴力匹配升级为线性的关键。3.3 Dijkstra 与堆的参数dist 数组的更新时机图论算法里Dijkstra 是单源最短路径的默认选择前提是边权非负。用优先队列实现时需要注意两个参数优先队列里存的是「(当前距离, 节点编号)」以及每次从堆顶取出节点时要判断该记录是否已经过期。过期判断是通过比较dist[v]与curDist完成的。// 邻接表存图graph[u] vectorpairint, intpair 为 (邻接点, 边权) vectorint dijkstra(int n, vectorvectorpairint, int graph, int src) { const int INF INT_MAX / 2; vectorint dist(n, INF); dist[src] 0; // 优先队列默认大顶堆用 greater 转成小顶堆按距离排序 priority_queuepairint, int, vectorpairint, int, greater pq; pq.push({0, src}); while (!pq.empty()) { auto [curDist, u] pq.top(); pq.pop(); if (curDist dist[u]) continue; // 这条记录已过期跳过 for (auto [v, w] : graph[u]) { if (dist[u] w dist[v]) { dist[v] dist[u] w; pq.push({dist[v], v}); } } } return dist; }这里的关键参数是if (curDist dist[u]) continue;这一行。由于同一个节点可能被多次加入优先队列只有距离最短的那条记录才需要展开邻居如果curDist大于当前已知最短距离说明这是一条被后续更新超越的旧记录直接丢弃。INF INT_MAX / 2是为了防止dist[u] w溢出这是图论算法里最常见的隐蔽 bug。另外普通 BFS 求最短路时用队列Dijkstra 用优先队列差别就在「弹出顺序」BFS 按层序弹出Dijkstra 按当前距离从小到大弹出。3.4 排序与剪枝什么时候排序什么时候剪枝排序算法本身是基础但总结时更值得写的是「什么时候先排序」。常见做法是题目涉及区间合并、求最大差值、贪心选择时先排序把无序问题变成有序问题。归并排序、堆排序、快速排序各有适用场景——归并排序适合外部排序和求逆序对堆排序适合数据流中取前 K 大快速排序的平均性能最好但最坏情况退化到 O(n²)。剪枝是回溯算法的优化手段核心思路是在递归搜索树的节点上提前判断「继续往下走是否还有希望」。经典场景是组合求和在进入下一层递归之前判断当前累计值是否已经超过目标值超过则直接跳过。另一个常用参数是「剪枝下界」如果剩余元素全部加上也不够达到目标也直接剪掉。这两类剪枝一个管上限一个管下限配合起来能把大部分无效递归挡在进入函数之前。4. 实例把一道题写成一份合格的总结条目4.1 条目结构题目信息、模式标签、复杂度、代码、变体一份可以放进「算法题总结 274」PDF 的条目至少应该有六块内容。题目信息和难度用来快速定位模式标签是检索入口一句话思路是给未来的自己看的代码必须是可运行的完整版本而不是片段复杂度分析要写清楚时间复杂度和空间复杂度分别由哪一步产生变体部分是拉分项写出这道题改一个条件后会变成什么另一道题。下面是一个条目模板的 Markdown 结构可以直接复制进自己的总结文档# 跳跃游戏 II - 来源LeetCode 45 / 面试高频 - 标签贪心算法 / 数组 / 最少步数 - 一句话思路每一步都贪心地扩展最远可达位置步数只在到达当前步右边界时增加 ## 复杂度 - 时间O(n)单次遍历 - 空间O(1)只需要两个变量 ## 代码 此处放可运行代码 ## 易错点 1. 循环条件是 i n - 1不是 i n 2. 当 i 到达 cur_end 时才步数加一而不是每次更新 next_end 都加 ## 变体 - 如果问能否到达终点跳跃游戏 I只维护最远可达位置 - 如果数组改为环形需要先判断是否永远跳不出再找起点4.2 以最短路径和二分法做对比总结时写什么一份总结写的到底是「题解」还是「模式」有一个非常直观的检验方法看变体部分。以「跳跃游戏 II」这道贪心算法经典题为例如果总结只写了代码和复杂度那它撑不起「总结」二字如果写了「当问题变成『最少步数』时为什么贪心仍然成立」读者就能把这道题的结论迁移到别的场景。同样二分查找算法的总结如果只写模板那几乎所有二分题都长得一样。应该在总结里写明什么时候用左闭右开什么时候用左闭右闭以及「找左边界」和「找右边界」分别是哪个分支要移动。很多面试题表面是「旋转数组找最小值」实际上考的是二分边界条件的掌握程度。我一般做法是每做一道新题先不看题解用自己的话写「一句话思路」写完代码后再对照别人的解法补充「是否还有更优解法」最后在变体栏里写一道与本题相似但条件不同的题。这个过程比刷三道新题拿到的提升更大因为它在强迫你做模式匹配而不是机械重复。4.3 常见总结误用抄题解、不写失败点整理算法题总结 274 这类 PDF 时最常见的失败是《小抄型总结》——把题解代码直接复制进来没有任何自己的思考痕迹。这种总结在整理的当下很有成就感因为内容越来越多但一个月后回翻你会发现根本读不下去因为代码旁边没有「为什么这么做」的注释。第二个常见问题是不写失败点。我见过很多人的总结里只有标准解法没有自己第一次提交时超时或越界的原因。建议每条总结至少保留一个「踩坑记录」字段——哪怕只是写「忘了处理空链表的极端情况」一行字这行字在面试前翻一遍的价值远高于抄一段官方解析。面试官问你「这道题哪里容易错」能答出失败点的人比能默写代码的人得分高得多。提示如果你真的只是想要一个可以快速翻阅的题单那按题号整理 PDF 是够用的但如果你想要的是面试前两个小时内能过完一遍的复习材料必须按模式重新组织且每个模式都有一句你自己的话。5. 让 274 道题的总结长期可用索引、复习节奏与自测整理完的总结要能真正用起来靠的不是意志力而是索引和节奏。我建议做三件事第一在 PDF 的第一页放一张模式索引表只保留「模式名 典型题ID 一句话结论」比如「二分查找 74/153/162左闭右开mid 用减法算」第二给每道题标记状态green表示能独立写出yellow表示需要提示red表示完全不会复习时只看红黄绿色题过一眼思路就跳过第三给自己定一个固定的复习节奏——新题做完后第 1 天、第 3 天、第 7 天各回看一次同类题之后每个周末把本周所有标红的题目重新默写一遍代码。如果你用的是 Markdown 维护原文可以写一个简单的脚本统计标红题目数量观察下降趋势。下面这个 Python 片段按「标签 状态」统计总结文件里的题目分布import re with open(algo_notes.md, encodingutf-8) as f: text f.read() # 匹配形如 - 状态red 的行统计三种状态的数量 status_counts { red: len(re.findall(r状态[:]\s*red, text)), yellow: len(re.findall(r状态[:]\s*yellow, text)), green: len(re.findall(r状态[:]\s*green, text)), } # 匹配形如 - 标签贪心算法 / 数组 的行按标签统计题量 tag_counter {} for line in text.splitlines(): m re.match(r\s*-\s*标签[:]\s*(.), line) if m: for tag in m.group(1).split(/): tag tag.strip() tag_counter[tag] tag_counter.get(tag, 0) 1 print(状态分布:, status_counts) print(标签分布:, tag_counter)配合一个简单的定时提醒每周五下午跑一次这个脚本如果red数量比上周多说明这周的新题没有消化需要减少刷题量、增加回看量。真正有效的刷题节奏不是「每天做 5 道新题」而是「新题与复习保持 1:2 的比例」。你能在面试前快速过完 274 道题的核心结论靠的是索引表和红黄绿状态而不是从头到尾重新看一遍代码。把这两样沉淀到你的 PDF 里这份总结才会从收藏夹里的文件变成你随时可以调用的能力。本文还有配套的精品资源点击获取
