← 全部材料 算法
ALGORITHM · 14 个必写题

写出答案只是及格线

同样的题,两个人写出同样的代码,分数可能差一档。差别在于有没有先复述、再说复杂度、主动列边界。这份把每类题的"该说什么"和"该写什么"放在一起。

01先看数据范围n 到十万级就排除 O(n²),这是比想最优解更实用的判断。
02记动作不记代码双指针是对撞,回溯就三步,堆是替换堆顶——记住动作就能推代码。
03边界主动说空输入、单元素、重复值。自己说出来是加分,被问出来是减分。
1

面试算法考的不是最优解★

考的是你能不能在有压力时仍然有章法
同样的题,两个人写出同样的代码,分数可能差很远。区别在于:一个人上来就写,另一个人先说思路、复杂度、边界,再写。面试官打分的依据是"过程是否可控",不是"答案是否最短"。
复述 + 确认把题意用自己的话说一遍,问清输入范围
→
说思路暴力解先说,再说优化方向
→
报复杂度时间、空间,以及瓶颈在哪
→
写代码边写边说,变量名有意义
→
手动跑例拿一个例子走一遍,再补边界
面试怎么答我一般先复述题意确认输入范围,然后给出暴力解和它的复杂度,再说优化方向。写之前会把边界列一下——空输入、单元素、重复值。写完用一个例子手动走一遍。这样即使最后没写出最优解,过程也是完整的。
2

双指针

有序数组和对撞扫描
双指针的价值是把 O(n²) 的枚举压成 O(n),前提是数组有序或者问题具有单调性。一旦发现"移动左指针会让结果单向变化",就可以砍掉一整个维度。
面试怎么答双指针把两重循环压成一重,前提是有序或者有单调性。两数之和有序版是左右对撞,和小于目标就左移、大于就右移。三数之和是排序后固定一个数,剩下的用双指针,重点在三处去重。
3

滑动窗口

连续子串问题的通用框架
滑动窗口解决的问题有同一个特征:求"连续"区间的最值。模板固定——右指针扩张、内层 while 收缩、过程中更新答案。写的时候只需要确定三件事:窗口里维护什么、什么时候收缩、答案在哪更新。
◐ 可交互:手动推一次滑动窗口
窗口和0
目标7
当前长度0
最优长度∞
数组 [2, 3, 1, 2, 4, 3],找出「和 ≥ 7 的最短连续子数组」。点右指针开始。
面试怎么答滑动窗口处理的是连续区间问题。模板是右指针扩张、内层 while 收缩、过程中更新答案。关键是窗口内的统计量要能增量更新,不能每次重算。最长型在收缩后更新答案,最短型在条件刚满足时更新。
4

链表题

指针操作,画图比想更快
链表题只有一个核心动作:改 next 之前先存住后面的节点。凡是写错的,都是因为先改了指针、把后半段弄丢了。会画三个格子加两个箭头,比在脑子里推要快得多。
面试怎么答链表题的核心是改指针前先保存后继节点。反转用 pre、cur、next 三个指针,最后返回 pre。判环用快慢指针,相遇即有环;找入口是相遇后一个指针回头部同速前进。这类题画三个节点两个箭头,比在脑子里想要可靠得多。
5

二叉树

四序遍历 + 递归返回值的设计
二叉树题分两类:遍历(按顺序访问)和递归返回值的设计(把子树的结果汇总上来)。前者背模板就行,后者才是区分度所在——要想清楚"我需要子树告诉我什么"。
面试怎么答二叉树题先判断是遍历型还是汇总型。遍历型背模板,前中后序只是 push 的位置不同,层序要用队列并先记下当前层节点数。汇总型的关键是定义递归函数的返回值含义,比如求深度就返回子树深度,求 LCA 就返回找到的节点或 null。
6

栈与单调栈

括号匹配是入门,单调栈才是高频
单调栈解决的是"找下一个更大/更小的元素",思路是维护一个单调的栈,谁破坏了单调就让谁出去。每个元素最多进出一次,所以是 O(n)。它不是靠记住,是靠想清楚"栈里存的是什么"。
面试怎么答括号匹配用普通栈,右括号必须和栈顶配对,最后栈要为空。单调栈用于找下一个更大或更小的元素,栈里存下标,谁破坏单调性就让谁出栈并结算答案。每个元素进出各一次,所以是线性复杂度。
7

哈希与 LRU★

LRU 是哈希加双向链表的标准组合
哈希表解决"查得快",但不解决"按访问顺序淘汰"。LRU 要同时满足 O(1) 查找和 O(1) 移动/删除,所以必须哈希存节点引用 + 双向链表维护顺序。单向链表做不到 O(1) 删除,因为没有前驱。
面试怎么答LRU 需要 O(1) 查找和 O(1) 的淘汰,所以要哈希表加双向链表:哈希存 key 到节点的引用,双向链表维护访问顺序,头部是最近使用,尾部是最久未使用。必须双向是因为删除要拿前驱。用哨兵节点能省掉大量判空。
8

二分查找

左右边界写不对,是因为边界定义没说清
二分的 bug 几乎都来自区间定义不一致:到底是 [lo, hi] 还是 [lo, hi)。先定死一种,然后 while 条件、mid 更新、返回值全部按它推,就不会乱。
面试怎么答二分的核心是先定死区间定义。我一般用左闭右开,所以 while 是 lo 小于 hi,收缩时 hi 等于 mid 而不是 mid 减一,返回 lo,它可能等于数组长度表示没找到。找边界就统一写成"第一个满足条件的位置",比记多个变体可靠。
9

排序:手写快排与归并

考的是分区和合并,不是背代码
手写排序只看两个动作:快排的分区(把小于基准的放左边)和归并的合并(两个有序数组合并)。能讲清这两个动作加复杂度分析就够了,剩下的细节(三路快排、随机基准)是加分项。
复杂度与稳定性对照快排 平均 O(n log n) 最坏 O(n^2)(已有序 + 固定基准) 空间 O(log n) 不稳定 归并 O(n log n) 稳定 空间 O(n) 稳定 堆排 O(n log n) 稳定 空间 O(1) 不稳定 冒泡 O(n^2),可提前退出成 O(n) 稳定
面试怎么答快排的核心是分区,选一个基准把小于它的换到左边,然后递归两边;平均 O(n log n),最坏是已有序且基准固定时退化成 O(n²),所以随机化基准。归并是分成两半分别排好再合并,稳定且最坏也是 O(n log n),代价是要 O(n) 额外空间。
10

动态规划★

先定状态,再想转移
DP 难在定义状态,不在写代码。判断能不能用 DP 看两点:最优子结构(大问题的最优解由小问题的最优解构成)和重叠子问题(同样的子问题会被算多次)。状态定义清楚后,转移方程基本是自然语言直译。
面试怎么答DP 的第一步是定义状态,我一般先问"我要用前 i 个元素表示什么",再让它带一个"以 i 结尾"的限定,否则没法转移。然后写转移方程,再定边界。判断能不能用 DP 就看最优子结构和重叠子问题这两条。
11

回溯

排列、组合、子集,一套模板三种剪枝
回溯就是带撤销的 DFS。模板固定:选择 → 递归 → 撤销。三类题只差两个参数:什么时候收集结果、下一层从哪里开始。去重靠"排序 + 同层跳过相同元素"。
三类题的差别只有两处 下一层起点 何时收集 子集 i + 1 每个节点都收集(包括空集) 组合 i + 1 或 i(可重复) rest 减到 0 时 排列 从 0 开始 + used path 长度等于 n 时
面试怎么答回溯的模板是选择、递归、撤销三步。子集和组合的差别在下一层的起点,排列要额外用 used 数组标记已选。去重的方法是先排序,然后在同一层跳过相同的元素。剪枝就是当剩余目标已经不够时提前 break。
12

堆与 TopK

为什么 TopK 用最小堆而不是排序
求前 K 个最大,维护一个大小为 K 的最小堆:堆顶是这 K 个里最小的,新元素比堆顶大就替换它。时间 O(n log K),空间 O(K)。当 K 远小于 n 时,比排序的 O(n log n) 好,而且能处理流式数据。
面试怎么答求 TopK 用大小为 K 的最小堆,堆顶是候选里最小的,新元素比它大就替换。复杂度 O(n log K)、空间 O(K),比全排序的 O(n log n) 好,而且能处理流式数据、内存受限的场景。求最小的 K 个则用最大堆。
13

图:BFS、DFS 与拓扑排序

网格题和依赖题的两个套路
图的题先判断是遍历还是依赖顺序。遍历用 BFS(求最短步数)或 DFS(求连通块);依赖顺序用拓扑排序(BFS 版的入度法最不容易写错)。网格题本质是四邻接的图,方向数组能省掉四段重复代码。
面试怎么答图题先分类:求最短步数用 BFS,求连通块用 DFS,求依赖顺序用拓扑排序。网格题就是四邻接图,用方向数组统一处理。拓扑排序用 BFS 的入度法,最后出队节点数不等于总数就说明有环。
14

复杂度估算与答题节奏

面试里的"够不够快"是怎么判断的
按 1 秒约 10^7 ~ 10^8 次基本操作估算:n 到 1e5 就必须 O(n log n) 或 O(n),n 只有 1e3 时 O(n²) 也过。先看数据范围再定算法,这是比"想到最优解"更实用的能力。
数据范围 -> 可接受复杂度n <= 10 指数级都行(O(2^n)、全排列) n <= 20 状压 DP O(2^n * n) n <= 100 O(n^3) n <= 1e3 O(n^2) n <= 1e5 O(n log n) n <= 1e7 O(n)
面试怎么答我会先看数据范围估复杂度,n 到十万级就必须 O(n log n) 以内,只有一千左右 O(n²) 也能接受。写完会主动报时间和空间复杂度,并说明瓶颈在哪。如果想到更优解但时间不够,会先把暴力解写完整再讲优化思路。
15

速答清单

十句话收口
01面试算法到底考什么?
考过程:能否复述题意、给出思路和复杂度、主动列边界、写完自测。不是考最短代码。
02双指针什么时候能用?
数组有序,或者移动指针时结果有单调性。无序时先排序,代价 O(n log n)。
03滑动窗口的模板怎么记?
右指针扩张、内层 while 收缩、过程中更新答案。窗口内的统计量要能增量维护,否则退化成 O(n²)。
04最长型和最短型的区别?
最长型在收缩完之后更新答案,最短型在条件刚满足时(while 内部)更新。写错就差一个元素。
05链表题最容易错在哪?
改 next 之前没保存后继节点。所以反转要三个指针,先存 next 再改指向。
06LRU 为什么必须用双向链表?
删除节点需要前驱才能 O(1)。单向链表只能从头找,就退化了。哈希存节点引用解决查找。
07二分怎么写不出 bug?
先定死区间是左闭右闭还是左闭右开,然后 while 条件、收缩方式、返回值全按这个定义推,不要混用。
08快排最坏什么时候发生?
数组已有序且基准固定时,退化成 O(n²)。解法是随机化基准或用三数取中。
09DP 第一步该做什么?
定义状态,而且要带上"以 i 结尾"这类限定,否则无法从子问题转移。再写转移方程和边界。
10TopK 为什么用最小堆?
求最大的 K 个,用大小为 K 的最小堆,堆顶是门槛。时间 O(n log K),空间 O(K),能处理流式数据。
11BFS 和 DFS 怎么选?
无权图最短路径用 BFS,连通性和枚举路径用 DFS。DFS 不保证最短。