🏠 总目录📚 本教程 附录A · 速查 ←
📑 本页目录(点开跳转)

附录A · 速查

⏱ 31 分钟 | ⭐ 临场想不起来时翻这一页:不变量一句话、模板一小段、写错了长什么样

📌 这一页是查的,不是读的。每一条都指回本板块的某一章。


🎯 一句话

这一页不讲怎么想出来,只把已经想出来的东西排好。 正文八章负责让你懂,这一页负责让你在白板前十秒钟内找回那句关键的不变量。

⚠️ 里面没有一行新代码 —— 全部从正文抄回,改了就对不上了。真要练,回到对应那一章把整段跑一遍。


🧭 一、先认出题型:数据形状 → 你真正要解决的问题

⭐ 00 章那条判据是这一页的骨架:一题先翻译成数据形状 + 一个变化动作 + 一个不变量,代码才会自己长出来。

类别 你真正要解决的问题 本板块代表题 章
链表 节点没有下标,怎样不丢路 反转、合并、环入口 01
树 一层分成多条路,怎样把结果带回来 深度、层序、路径和、最近公共祖先 02
栈与队列 只能从一端拿时,怎样记住「最近」 两栈队列、min 栈、滑动窗口最大值 03
搜索与排序 不枚举全部,怎样砍掉不可能的半边 二分边界、旋转数组、最小 k 个数 04
动态规划 今天的最优,怎样由昨天的状态推出 最大子数组和、跳台阶、正则匹配 05
回溯 有分叉时,怎样试完一条再干净地回来 矩阵路径、机器人范围、排列 06
位运算与模拟 规则很机械,怎样逐步翻译而不漏边界 异或分组、二进制加法、螺旋矩阵 07
字符串 输入很脏,怎样一格一格地吃进去 atoi、最长无重复子串、字符流 08

📋 「我现在卡在哪」→ 直接跳哪一章:

你的症状 去哪
指针、链表一看就晕 01 链表
递归一写就炸 02 树
想学面试常用模板 03 栈队列与窗口 → 04 二分排序与堆
想练「怎么想出来」 05 动态规划 → 06 回溯与矩阵
想补零碎高频题 07 位运算与模拟 → 08 字符串与输入

🧩 二、⭐ 不变量卡:每题一句,忘了就看这一句

⭐ 这是全页最该扫的一节。 每一条都是正文里那句「循环里始终为真的事」。

题 一句话不变量 章
反转链表 每轮结束后,prev 指向已反转的前缀,cur 指向尚未处理的后缀 01
合并两个有序链表 tail 后面永远是已排好序的结果;a、b 各指向还没被拿走的最小节点 01
链表中环的入口 快慢相遇 ⇒ 有环;一个回头,两人同速再见就是入口 01
二叉树深度 depth(node) 永远返回「以 node 为根的子树有多高」,父节点不必知道孩子内部怎么数 02
层序打印 队列里排着的就是「先来的先处理」 02
路径和 path 始终是当前这条根到节点的路:进节点 append,离开节点 pop 02
最近公共祖先 递归交回「在这棵子树里找到的目标节点,或已经确定的公共祖先」 02
两个栈实现队列 outbox 顶部永远是当前最早进入、还没出去的元素 03
min 栈 mins 的每一层记「走到这一层为止的最小值」 03
滑动窗口最大值 队列里只留可能成为最大值的人;⭐ 存的是下标不是值 03
lower_bound 答案永远在半开区间 [left, right) 里;right 是「答案可能到这里为止」的边界 04
旋转数组最小值 中点和右端比:中点更大 ⇒ 最小值在右边;否则在左边或就是中点 04
最小的 k 个数 只维护 k 个候选,不必把全部排好 04
连续子数组最大和 best_ending_here 是「必须以当前位置结尾」的最大和,它不等于全局答案 05
跳台阶 到第 n 阶,最后一步不是从 n−1 来就是从 n−2 来 05
正则匹配(. 和 *) * 有两条路:跳过「字符+星号」,或吃一个字符后继续留在原模式 05
矩阵中的路径 一个格子在当前路径中不能重复使用:进来标记、离开还原 06
机器人的运动范围 已经访问过的格子不用重算;条件是坐标位数之和不超过阈值 06
字符串的排列 每一层决定当前位置放谁;同一层遇到重复字符时跳过 06
只出现一次的两个数字 全部异或后只剩两个目标的异或;mixed & -mixed 取最低位的 1,两个目标在这一位必不同 07
不用加减乘除做加法 异或是不进位的和,& 左移一位是进位;重复到没有进位 07
顺时针打印矩阵 不转方向,每绕一圈收缩上、右、下、左四条边;每走一条前确认矩形还没塌 07
atoi 四个阶段固定:跳空格 → 读正负号 → 连续读数字 → 处理溢出;任一步不符合规则就停止,不「尽量猜」 08
最长无重复子串 左边界只向右:遇到重复就跳到它上次出现位置的后一格 08
字符流第一个不重复 计数负责判断重复,队列负责保留先后;队首重复了就淘汰 08

🧯 三、⭐ 写错了长什么样:症状 → 它到底在说什么

用法:跑出错误答案或异常之后,在这一节找你看到的那句话。 ⚠️ 这类题最难的地方是多数错误不报异常,只是答案不对 —— 所以下面前半截是「症状」,后半截才是异常。

链表反转后只剩一两个节点,或者结果里出现 None

⭐ 先写了 cur.next = prev,再回头去找原来的 cur.next。 那时后路已经被覆盖,尚未处理的整个后缀就再也找不到了。 → 顺序永远是:nxt = cur.next 先保住后路 → cur.next = prev 再掉头 → 两队伍一起向前。见 01 章题 1。

合并链表时要写「第一个节点」的特殊分支,越写越乱

→ 用 dummy 假头。⭐ 它存在的唯一理由:让第一个节点和后面的节点一样接,不必写「第一次特殊处理」。见 01 章题 2。

💀 树的路径题:答案条数对,但内容全是空列表

⭐ answer.append(path) 写成了没有 [:] 的版本。 所有答案都指向同一个还会继续变化的列表,回溯弹完之后它们一起变空。 → 存答案必须 answer.append(path[:])。见 02 章题 3。

树的路径题:右子树的答案里混进了左边的节点

离开一个节点时没有 path.pop()。同一个 path 被左右子树共用,不复原现场,右子树会带着一段不属于它的旧路径继续走。见 02 章题 3。

滑动窗口最大值:窗口滑出去了,答案还是旧的那个最大值

⭐ 队列里存的是值而不是下标。 存下标不是细节 —— 只有知道下标,才判断得出队首是否已经离开窗口(queue[0] <= right - k)。见 03 章题 3。

IndexError: queue is empty(两个栈实现队列)

倒完 inbox 之后 outbox 仍然是空的 —— 队列本来就没有元素。⭐ 注意倒的时机:只有 outbox 空了才整批倒,中途倒会把顺序搅乱。见 03 章题 1。

二分:数组里没有目标时结果不对,或者目标比所有元素都大时越界

⭐ 右边界写成了 len(nums) - 1。 lower_bound 用的是半开区间 [left, right),right = len(nums) 才表达得出「目标比所有元素都大」时的插入位置,也让空数组和边界判断统一。见 04 章题 1。

ValueError: k out of range(最小的 k 个数)

k 不在 0 <= k <= len(nums) 里。⚠️ 这是故意加的边界检查,不是 bug —— 面试里被追问边界时,它就是你的答案。见 04 章题 3。

最大子数组和:只维护了一个变量,答案偏小

⭐ 只记全局最大和,丢掉了「以当前点结尾」的那个状态。 下一轮需要知道前缀能不能接上,而全局最大和可能结束在很早的位置,不能直接接。见 05 章题 1。

递归的正则匹配跑得非常慢

同一批子问题被反复算。⭐ 看见「很多子问题重复算」先考虑记忆化,它就是从递归过渡到动态规划的桥。见 05 章题 3。

矩阵路径:某条真实存在的路径找不到

⭐ 走完一个分支后没把 board[r][c] 恢复回去。 这个格子只是当前分支不能再用;别的起点或兄弟分支仍然应该可以用它。见 06 章题 1。

排列结果里有重复项

同一层里同一个字符被试了不止一次。→ 每层用一个 used 集合,同一层相同字符只尝试一次。见 06 章题 3。

螺旋打印:单行或单列的矩阵,某些元素被打印了两次

⭐ 走下边和左边之前少了那两次边界判断。 只有一行时上边已经把元素拿完了,只有一列时右边已经拿完了。见 07 章题 3。

二进制加法在负数上死循环 / 结果离谱

⚠️ add_without_plus 那段对非负整数最直观。 Python 的负数是无限位语义,进位可以一直左移下去。面试若要求处理负数,要先约定 32 位掩码。见 07 章题 2。 ⭐ 反面在 NumPy · 08 整数 dtype 的真相:一进 NumPy 整数就是定宽的,越界不报错、直接回绕,而且没有任何信号。

atoi:'words and 9' 返回了 9

⚠️ 你在「读到非法字符」之后还继续找数字了。⭐ 四个阶段是顺序的,任何一步不符合规则就停止,不是「尽量猜」。正确结果是 0。见 08 章题 1。

最长无重复子串:结果比正确答案小

左边界回退了。⭐ 右边界已经向右扩展;回退左边界只会把已知会冲突的字符重新放回窗口,既无帮助又会重复计算。条件要写成 last_seen[char] >= left。见 08 章题 2。


📦 四、这套题用到的标准库,一张表

⭐ 本板块只用了三个 collections / heapq 的东西,每个都对应一个明确的需求:

用到的 干什么 出现在
collections.deque 两端 O(1):BFS 的队列、滑动窗口的单调队列、字符流的先后 02 层序 · 03 窗口 · 08 字符流
collections.Counter 计数,用来判断「重复了没有」 08 字符流
heapq.nsmallest 只要前 k 个最小元素,不必全排 04 最小的 k 个数
functools.lru_cache 记忆化,把重复子问题的递归变成 DP 05 正则匹配

⚠️ 面试若要求手写「最小的 k 个数」,用大小为 k 的最大堆;但先把「只维护 k 个候选」这个思想弄懂,容器是次要的。 ⭐ deque 为什么在这些题里非它不可(list.insert(0, x) 是 O(n)、deque.appendleft 是 O(1),实跑差 299~1005 倍),在 Python 会咬你的地方 · 09 容器、哈希与顺序。


🧪 五、每题的小闭环,和「做完了」的标准

⭐ 00 章定的四步,卡住时按它走:

  1. 先用 3–6 个元素手画一次,不急着敲代码。
  2. 用一句话写下循环里「始终为真」的事 —— 那就是不变量(第二节整节都是它)。
  3. 把代码复制运行,至少换一个边界输入:空、一个元素、重复、极值。
  4. 卡 10 分钟时先看「关键不变量」,不要直接滚到完整代码。

⭐ 完成标准不是 AC 过一次,而是第二天遮住代码后还能说出:指针 / 下标各代表谁、每轮为什么不会漏。

📋 每类题该补的边界输入:

类别 至少再试这几种
链表 空链表、只有一个节点、没有环
树 空树、只有左子树的斜树、目标不在树里
栈队列 空时取值、k 等于数组长度
二分 目标不存在、目标比所有元素都大、数组只有一个元素
DP n = 0、全负数数组
回溯 目标字符串比矩阵还长、阈值为 0
位运算 负数、0
字符串 空串、全是同一个字符、开头就是非法字符

🔗 这一页连到哪里

相关的地方 为什么
00 · 怎么刷代码题 第一节那张题目地图和第五节那四步的完整版
Python 会咬你的地方 · 00 ⭐ 这里的代码全是 Python。被「改了 b 结果 a 也变了」「默认参数记住了上次的值」咬过的话,那一套讲的就是它们
Python 会咬你的地方 · 09 · 容器、哈希与顺序 第四节那三个容器各自的复杂度和保不保序:dict 从 3.7 起保证插入序,set 从来不保证
机器学习与深度学习基础 · 附录C · 手撕代码速查 ⭐ 另一类白板题:这里练链表 / 树 / DP,那里是「用纯 numpy 写一个多头注意力」。算法岗两类都会被问
不靠数据的 AI · 05 · 状态空间与搜索框架 ⭐ 「谁应该先出来」在那边是一个统一骨架:BFS、DFS、迭代加深、一致代价、A* 只差「待展开集合用什么容器」
不靠数据的 AI · 14 · 回溯与约束传播 同一个回溯骨架往上加两件本板块没有的东西:前向检查、弧相容
强化学习基础 · 04 · 动态规划 ⚠️ 同名不同物,但共用那句「今天的最优由昨天的状态推出来」
NumPy 与向量化思维 · 08 · 整数 dtype 的真相 第三节最后那条的反面:定宽整数越界静默回绕

⭐ 这一页最该带走的一句:题目换名字没关系;数据形状和不变量没换,解法就还在。

下一节 👉 回到板块索引

打卡记录保存在你的浏览器里,首页能看到总进度