
简介面向算法面试的系统性刷题资源整合剑指Offer题解、程序员代码面试指南、九章算法讲解与牛客直通BAT课程的核心内容并收录大公司笔试真题编程题适合准备校招或社招技术岗的开发者。资源共969个文件主体为468个Java源码与493个编译生成的class文件另含少量md、txt、docx等说明文档压缩包仅789KB便于本地对照源码与运行结果进行学习。目前已有41人下载学习。代码覆盖二叉树、动态规划、最大子矩形等经典高频题型既包含第一遍跟随课程的学习实现也包含两个月后复习时的重新实现两批代码可对照阅读能清晰看出思路演进与查漏补缺的过程对系统梳理数据结构与算法知识、冲刺技术面试有较高参考价值。1. 数据结构与算法刷题全攻略先读题解还是先重写数据结构与算法刷题全攻略这份资料我拿到手先看目录剑指Offer题解、程序员代码面试指南题解、九章算法讲解、牛客直通BAT算法课以及两轮代码——第一遍学习代码和两个月后复习全部重新实现。最戳我的不是题解而是后两轮代码。刷题备考算法面试资料多少从来不是胜负手同一个题解有人看一遍就合上有人会在第二遍让自己不查资料重新实现。两轮代码之间的两个月才是真正把算法从“见过”变成“会写”的窗口。如果你也在准备校招或算法工程师面试手里资料比我还多先停下来想一想这些资料是要“看完”还是要“变成你自己的代码”。2. 从剑指Offer题解到牛客直通BAT四类资料怎么排优先级这份归档里的资料实际分成四层剑指Offer题解贡献高频题集程序员代码面试指南题解贡献一题多解和面试官视角九章算法讲解贡献系统框架牛客直通BAT算法课和笔试真题贡献实战手感。很多人把四样排成先后顺序全刷结果一轮没结束就疲了。我建议按“覆盖率优先”排优先级先只做剑指Offer里的高频题配合最小必要的数据结构基础再去听九章算法讲解补套路等能独立写出中等题再用程序员代码面试指南做优化最后用牛客直通BAT的模拟题做笔试冲刺。资料角色核心教的东西建议放在哪一轮剑指Offer题解高频面试题的标准解法和边界第一遍建立最小模板集程序员代码面试指南题解一题多解、复杂度优化、面试官追问第二遍重写时对照提升九章算法讲解按算法主题讲套路如双指针/BFS/动态规划听完课马上用题单检验牛客直通BAT 笔试真题限时笔试环境、多组输入输出投递前两周做模拟2.1 第一遍怎么学把题解转成可回忆的笔记而不是收藏第一遍学习代码不等于抄代码。常见做法是读题后先自己想十五分钟然后看题解关掉题解在编辑器里写一遍跑通后做笔记。笔记写“卡点”而不是最终答案。比如反转链表卡在“保存后继”层序遍历卡在“一次取完当前层”。用一个状态脚本记录每道题的进度比文件夹分类更可靠# 刷题状态跟踪按 tag 统计一刷和二刷数量 import json from collections import Counter records { 重建二叉树: {tag: 二叉树, first_done: True, second_done: False}, 用两个栈实现队列: {tag: 栈与队列, first_done: True, second_done: True}, } def stat(records): tags Counter() for name, r in records.items(): if r[first_done]: tags[r[tag] _一刷] 1 if r[second_done]: tags[r[tag] _二刷] 1 return tags for k, v in stat(records).items(): print(k, v)逻辑说明records 每一项对应一道题tag 决定它属于哪类算法first_done 和 second_done 是布尔开关。这个脚本每周跑一次能立刻看出哪个分类“一刷多、二刷少”然后针对性补。参数说明tag 直接写中文代码里没有额外映射如果题量上千建议把 records 从 json 文件读入而不是硬编码。一刷不追求数量追求每道题都留下“卡在哪”的记录二刷才知道往哪里看。2.2 两个月后重写的节奏把背会和会了分开二刷最忌直接打开旧代码。我一般设置四十五到六十天的间隔正好对应遗忘曲线里最陡的一段。操作上把旧目录改名新开 second_pass 目录在没有旧代码的情况下写。写不出来就看笔记里的卡点再看题解但不允许看旧代码。启动命令如下# 把一刷代码归档避免二刷时下意识模仿 mv first_pass/剑指Offer archive/first_pass_剑指Offer mkdir -p second_pass/剑指Offer cd second_pass/剑指Offer说明mv 不是删除保留原始实现用于最后对比但归档目录与工作目录分离手边只有题目和空白文件。写完后用diff -u archive/first_pass_剑指Offer/xxx.py second_pass/剑指Offer/xxx.py对比差异如果二刷代码只是旧代码换壳diff 很短说明这次重写没有产生新知识。真正有意义的二刷代码应当出现更清晰的注释、更短的边界判断甚至更省空间的写法。2.3 九章算法与程序员代码面试指南两套方法论怎么互补九章算法讲的是题型的算法原型看到最长递增子序列知道要动态规划看到括号生成知道要回溯看到岛屿问题知道要DFS/BFS。程序员代码面试指南则是面试官视角给出同一个问题从暴力到最优的演进路线并解释每一步为什么能去掉一个循环。我的用法是九章的课放在晚上看第二天上午用套路做三道题程序员代码面试指南放在二刷阶段每做完一道题翻对应章节确认有没有遗漏更优解。不要把两套讲解混在一遍里读那样信息量太大大脑会进入“看了不等于会写”的假性掌握状态。二刷时用程序员代码面试指南做验算你的实现和它的思路对比如果只有一行差异说明方法掌握如果你连它的状态转移都没看懂说明这道题还没消化需要三刷。3. 两个月后重新实现代码链表、二叉树与排序的复现要点二刷的核心是“全部重新实现代码”不是把一刷代码跑一遍。这章选三类最常被手撕的题单链表反转、二叉树层级遍历、堆排序与归并排序。这三类在剑指Offer题解和程序员代码面试指南里反复出现也很适合验证你是否真理解了递归、队列和比较排序的过程。二刷时每题给自己二十五分钟写不出来先记录卡点再补写不要当场翻开题解。3.1 单链表反转的三版递进从穿针引线到递归边界一刷多数人能写迭代版二刷我建议把三个版本都写一遍双指针迭代、头插法、递归。双指针是基础必须在纸上标出 prev、cur、nxt 三个指针每一步的关系头插法适合展示链表操作熟练度递归版本则检验递归设计能力。先看最常用的双指针class ListNode: def __init__(self, val0, nextNone): self.val val self.next next def reverse_list(head): prev None cur head while cur: nxt cur.next # 先保存后继否则改指针后会丢 cur.next prev # 当前节点掉头 prev cur # 前驱前移 cur nxt # 当前节点后移 return prev # 终止时 prev 是原链表尾逻辑说明核心是第 4 行。不保存 nxtcur.next 被改写后 cur 就找不到下一个节点循环只能停在原地。参数说明head 为空时循环不执行直接返回 prev 也就是 None单节点时 nxt 为 Nonecur.next 指向前驱 prev最终返回原节点天然正确。二刷应该默写出这段然后手写递归版def reverse_list_rec(head): if head is None or head.next is None: return head new_head reverse_list_rec(head.next) head.next.next head head.next None return new_head说明递归终止条件是“空节点或最后一个节点”返回的是新链表头回溯时让 head 后继的 next 回头指向 head再把 head.next 置 None 防止环。注意递归处理超长链表会爆栈实际笔试如果没要求用递归优先写迭代版。常见扩展是“反转前 N 个节点”需要在递归里记录后继属于这个模板的变体。3.2 二叉树层级遍历与双端队列一个模板吃透三类变体二叉树层级遍历是剑指Offer里出现频率极高的题也是树加BFS的入口。九章算法把这类题归为“BFS分层次遍历”模板用队列承载当前层先取本层长度再一次性弹出本层全部节点。二刷时必须理解为什么不用 list 的 pop(0) 而用双端队列from collections import deque def level_order(root): if not root: return [] res [] q deque([root]) while q: level [] n len(q) # 进入本层前先锁定量 for _ in range(n): node q.popleft() level.append(node.val) if node.left: q.append(node.left) if node.right: q.append(node.right) res.append(level) return res逻辑说明n len(q)在 for 循环前取值是因为循环里会继续往 q 添加下一层节点如果不锁定量循环会越滚越长把整棵树一层全输出。双端队列的 popleft 是 O(1)普通 list 的 pop(0) 需要搬运后面全部元素元素量大时会成为性能黑匣子。变体一之字形遍历只需要在输出 level 前判断当前层奇偶再 reverse变体二右视图只取每层最后一个节点变体三最大宽度需要把节点下标存进队列。三个变体面试中经常追问二刷要从同一个模板改而不是背三种代码。3.3 堆排序与归并排序为什么二刷要手写而不是调库笔试真题很多题要求 O(n log n)库函数 sort 能过题但面试官一旦追问“讲讲排序过程”现场就得能写。数据结构与算法里最值得手写的是堆排序和归并排序前者对应堆结构和 TopK 问题后者对应分治和合并有序序列。堆排序二刷我一般先写调整函数def heapify(arr, n, i): largest i left 2 * i 1 right 2 * i 2 if left n and arr[left] arr[largest]: largest left if right n and arr[right] arr[largest]: largest right if largest ! i: arr[i], arr[largest] arr[largest], arr[i] heapify(arr, n, largest) def heap_sort(arr): n len(arr) for i in range(n // 2 - 1, -1, -1): heapify(arr, n, i) for i in range(n - 1, 0, -1): arr[0], arr[i] arr[i], arr[0] heapify(arr, i, 0) return arr参数说明n // 2 - 1 是最后一个非叶子节点下标从它开始向上建堆heapify(arr, n, i) 中 n 表示当前堆大小i 是待调整位置。每次元素交换后要向下递归调整保证父节点值大于两个子节点。第二层循环把堆顶最大值交换到末尾同时堆大小减一逻辑上等价于逐个弹出最大元素。容易翻车的细节是 right 的越界判断漏掉 right n 会访问不存在的下标。归并排序的二刷重点是 merge 两个有序数组它同时也是合并两个有序链表、求逆序对的基础。写法上先递归拆分到长度为 1再在回溯时合并def merge_sort(nums): if len(nums) 1: return nums mid len(nums) // 2 left merge_sort(nums[:mid]) right merge_sort(nums[mid:]) merged [] i j 0 while i len(left) and j len(right): if left[i] right[j]: merged.append(left[i]) i 1 else: merged.append(right[j]) j 1 merged.extend(left[i:]) merged.extend(right[j:]) return merged说明合并时用 可以保证稳定排序二刷常在这里翻车的是忘记处理剩余部分导致最后几个元素丢失所以 extend 不能省。递归拆分的空间复杂度是 O(n)如果题目限制不能用额外数组要改写为原地归并那种写法另有一套边界不在刷题初期要求。二刷时同时手写这两个排序不是为了现场造轮子而是让你遇到“数组第K大”“最大K个元素”等变形题时脑子里立刻出现可调整的模板。4. 大公司笔试真题编程题输入输出与时间限制的实战细节剑指Offer题解和九章算法讲解里的代码通常只写核心函数主函数和输入输出全被平台隐藏。但牛客直通BAT和大厂笔试真题不一样给你一个在线判题页面代码要从标准输入读取再写到标准输出。一刷阶段我在这上面吃过亏算法逻辑对了输入格式没读懂整道题零分。这里讲三个细节二刷时一定要亲手练。4.1 多组输入与终止条件先把牛客样例题读明白大厂笔试最常见的是“多行输入直到文件结束”。C 传统写法是 while (cin x)Python 常见是 for line in sys.stdin。很多人只写了一次输入遇到多组用例直接报错。最稳妥的模板是先读整份输入再按行切import sys for line in sys.stdin: line line.strip() if not line: continue a, b map(int, line.split()) print(a b)逻辑说明sys.stdin 是可迭代的每次 for 取一行包含末尾换行符strip 去掉它空行跳过防止最后一行只有一个换行。参数说明line.split() 默认按任意空白切分能同时处理空格和制表符如果一行里混着多余空格默认 split 也安全。另一种常见格式是第一行给测试组数 T后面跟着 T 组数据这时不能再用 for line 一路读到 EOF而是先读 Timport sys def solve(n): return n * n data sys.stdin.read().split() t int(data[0]) idx 1 for _ in range(t): n int(data[idx]) idx 1 print(solve(n))说明把整个输入 split 成 token 列表然后按下标顺序取适合“T N 数组”这类结构idx 是游标每读完一个字段就后移。别小看这个模板真题里经常出现第一行 T、第二行开始是长度 N 和 N 个数读错一个下标就会全部错位。4.2 快读与内存限制把大厂真题的时间卡点拆开同样的算法Python 用 input() 和 print() 在 10^5 以上数据会慢一个量级。二刷阶段我建议直接养成固定习惯数据量大就用 sys.stdin.buffer.read() 一次性读入输出用列表暂存后一次 join 打印。C 则关掉流同步#include bits/stdc.h using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; vectorint a; while (cin n) { a.resize(n); for (int i 0; i n; i) cin a[i]; sort(a.begin(), a.end()); for (int i 0; i n; i) { if (i) cout ; cout a[i]; } cout \n; } return 0; }参数说明sync_with_stdio(false) 让 C 流不再同步 C 的 stdio输入输出接近纯 C 的速度cin.tie(nullptr) 解除 cin 和 cout 的绑定避免每次 cin 前主动 flush cout交错读写时提速明显。vector 用 resize(n) 而不是反复 push_back是预分配内存避免扩容复制。排查内存问题时看题目限制是 128MB 还是 256MB一个 10^6 的 int 数组是 4MB但 10^6 个嵌套 vector 的 overhead 可以到几十 MB二刷时要注意数据结构的实际占用。4.3 暴力枚举到剪枝笔试真题里怎么保住部分分值刷题久了容易只看最优解但笔试现场碰到没见过的题暴力枚举永远是最先能写出来的方案。真题判分通常按用例通过比例给分过了部分用例就有部分分。暴力枚举的关键不是无脑全试而是通过排序和剪枝把它从“全部超时”改进为“小数据全对、大数据不崩”def combination_sum(candidates, target): res [] candidates.sort() n len(candidates) def dfs(start, path, rest): if rest 0: res.append(path[:]) return for i in range(start, n): if candidates[i] rest: break # 剪枝已经排序后面的都更大 path.append(candidates[i]) dfs(i, path, rest - candidates[i]) path.pop() dfs(0, [], target) return res逻辑说明枚举从 start 开始保证同一个组合不会被位置调换产生两次候选数组排序后当前值大于剩余目标就直接 break后面的更大更不可能是解这就是最常见的剪枝。参数说明rest 是剩余目标初值由外部传入 targetpath[:] 拷贝防止后续 pop 修改已记录结果start 传 i 而不是 i1是因为这类题允许同一个数重复使用若不允许重复把那一行改成 dfs(i 1, ...) 即可。笔试时如果目标值小、候选数多还可以加记忆化但第一版先把枚举写对再谈优化。5. 刷题项目避坑自查二刷计划最容易翻车的 5 个点二刷的坑比一刷多因为一刷是“从无到有”错了不心疼二刷是“从有到对”很容易把“看过旧代码”误当成“会写”。按我拆解这份归档项目的经验下面 5 个现象最典型每个按现象、原因、解决来讲。5.1 现象一刷代码全部能跑面试现场手写直接卡住原因一刷阶段编辑器、语法提示、报错信息都在大脑用“识别错误”代替了“构造代码”真正面试是白板或空白文档没有这些辅助。解决二刷设定为“不查资料、不跑测试、先写完整代码”的模式写完再回到编译器验证。最佳间隔是四十五到六十天太短等于复读太长等于重学。衡量标准不是读过几遍而是合上代码能写出几行。5.2 现象三个月后重新实现变成对着旧代码翻译原因旧代码就在旁边大脑会优先参考旧结构二刷产出的代码只是变量名换了换失去重写意义。解决开工前把旧代码物理归档到独立目录命令如下mv first_pass/剑指Offer ~/archive/data-structure-algo-first-pass mkdir -p second_pass/剑指Offer写完后用diff -u ~/archive/data-structure-algo-first-pass/剑指Offer/reverse_list.py second_pass/剑指Offer/reverse_list.py对比。如果 diff 只有几行注释差异说明这次二刷是在旧代码上改的不算重新实现如果核心逻辑明显不同这次重写才有价值。注意归档路径要对应正确避免误把文件移入混合目录我整理时习惯统一加日期前缀。5.3 现象牛客直通BAT算法课听了两遍做中等题还是没思路原因视频是被动输入只有自己动手提取题目特征才能触发主动回忆连续看课会进入“我都听懂了”的假象区。解决视频每小节讲完一道题就停下来先把该题独立实现再做一道同主题变体。比如讲了双指针就做“最长无重复子串”讲了动态规划就做“编辑距离”。九章算法也一样它的强项是归纳题型不负责替代你的练习量。5.4 现象KMP、并查集这类模板一学就会一做就忘原因模板题没有边界测试你以为记住了 next 数组的求法实际只记住了教材截图。解决二刷模板后立刻补断言代码把边界用例写死在文件里。比如 KMP 的 next 数组def build_next(p): nxt [0] * len(p) j 0 for i in range(1, len(p)): while j and p[i] ! p[j]: j nxt[j - 1] if p[i] p[j]: j 1 nxt[i] j return nxt assert build_next() [] assert build_next(a) [0] assert build_next(aaaa) [0, 1, 2, 3] assert build_next(ababc) [0, 0, 1, 2, 0]现象说明assert 会指出哪个用例失败空串和单字符边界最容易在二刷时漏掉。原因在于 while j and p[i] ! p[j] 在 j 为 0 时直接跳过不会越界但很多人把 j 初始化为 -1 的版本写混。解决方式选定一种下标从 0 开始的写法每次二刷都只用这一种不要来回切换教材版本。5.5 现象lintc.zip 整理得井井有条连续三个月没有打开原因归档动作产生“已完成”错觉解压后再也没有下一步触发。数据结构和算法刷题项目的价值只有在你重新打开代码那一刻才兑现。解决把复习做成日历任务而不是文件夹分类。每十天从 second_pass 里挑三道已通过题重写并跑对数器每三十天抽查一道以前卡住的题。归档文件命名用具体日期例如 2024-剑指Offer第一轮配合提醒更有效。我自己的习惯是每月初把第二遍代码目录整体读一遍看注释和变量名能不能让我十秒内回忆起这道题。6. 用随机对数器卡住二刷质量一个可量化的验证习惯二刷完成不等于验证完成。只跑官方样例是最低标准很多边界只有随机数据能撞出来。手上有这份刷题全攻略的两轮代码最好用的验证工具就是随机对数器写一个最优解再写一个明显正确但慢的暴力解喂同样的随机输入断言输出一致。比如最大子段和import random def brute(nums): best float(-inf) for i in range(len(nums)): s 0 for j in range(i, len(nums)): s nums[j] best max(best, s) return best def optimized(nums): best cur float(-inf) for x in nums: cur max(x, cur x) best max(best, cur) return best for _ in range(1000): n random.randint(1, 10) nums [random.randint(-50, 50) for _ in range(n)] assert brute(nums) optimized(nums), (nums, brute(nums), optimized(nums)) print(1000 组随机用例通过)这里的参数说明n 限制在 1 到 10保证暴力解 O(n^2) 瞬时完成随机数范围包含负数和正数专门测全负数、全正数、交替符号这几类边界。断言失败直接打印输入和两个输出这就是可复现的最小用例。数据量和数值范围可以根据题目类型调整但原则不变暴力解必须先保证自己正确。我现在拿到任何二刷项目都要先把新旧实现各放一个文件跑一遍随机对数器才敢说“会了”。这个习惯帮我避免了多次面试现场翻车。希望帮到你。本文还有配套的精品资源点击获取