简介:一份面向计算机考研408与893自命题考生的数据结构算法题总结,36页PDF浓缩了数组、链表、栈、队列、二叉树等核心结构的常考题型,涵盖合并排序数组、约瑟夫环、栈实现队列、最小栈、循环队列、链表删除/反转/环入口、二叉树前中后序与层序遍历、排序与Top K问题、双指针、二分查找、贪心及动态规划等机试高频题。每个题目给出C语言实现与关键思路,适合对照练习和快速复盘。内容按数组合并排序、链表操作、二叉树遍历、排序算法、递归与非递归、图遍历等模块清晰编排,基本覆盖考研算法题的主要出题方向;代码风格简洁,便于直接理解运行逻辑。资源本身是单个PDF文件,共36页,约1.67MB,方便在手机、平板或电脑上阅读,也适合打印成纸质版做冲刺笔记。目前已有1681人学习下载,对于正在刷leetcode或准备自命题机试的同学,是一份高性价比的浓缩复习资料,能节省大量整理时间并快速定位薄弱环节。
1. 考研数据结构算法题总结36页,拿到手先看什么?
九月中旬我开始刷408真题,发现最耽误时间的不是选择题,而是每年最后那道15分的代码大题。当时手上资料不少,但没一份能直接告诉我“考场上怎样把代码写到不丢分”。《考研数据结构算法题总结36页(893+408)》是我复习后期翻得最勤的一份资料,它把两套考纲交叉后的高频算法点按题型压成36页,每一页都是“题干+思路+代码+复杂度”四件套。适合两类人:跨考、手写代码心里没底的人;复习时间紧、想快速找回手感的人。先说结论:它替代不了真题,但能帮你把代码题从“玄学”变成“公式”。
2. 先拆408和893的命题差异:这份总结按什么逻辑编排
2.1 408统考算法题的命题边界:两道大题、复杂度红线
408统考的数据结构大约占45分,代码题一般出现在综合应用题里,通常是最后一道15分的大题,偶尔前面还会有一个小的算法设计填空。题目风格是“给你一个场景,写核心代码并分析复杂度”,比如给定一个链表,要求O(n)时间删除倒数第K个结点。这种题LeetCode上有原型,但考场上没有测试数据,阅卷只看三段:逻辑是否完整、边界是否覆盖、复杂度是否达标。
复杂度是408明确会关注的一条线。评分标准里经常写着“时间复杂度O(n)得满分,O(n^2)给一半”。所以这份总结里每道题都单独标注了时间复杂度和空间复杂度,我当时的做法是每页先看复杂度要求,再看题目描述,最后才对思路。如果你和我一样是从代码量开始复习的,建议把这个顺序反过来,先逼自己养成看复杂度的习惯。
提示:408代码题的复杂度红线一般是O(n)或O(n log n)。如果题目没给数据规模,默认不能出现双重循环。
从覆盖内容看,36页对应的是考研数据结构的七个章节:线性表、栈与队列、串、树与二叉树、图、查找、排序。看起来和各教材目录一样,但它的编排不是按章节,而是按“考察方式”走。比如链表和数组被放在同一组,因为它们经常以相同题型出现;图的遍历和树的遍历也归在一起,因为套路一样。这是这份资料和教材目录最大的区别:教材按知识结构组织,它按答题套路组织。
2.2 893自命题的差异:偏基础还是偏综合
如果说408是“全国统一难度”,893就是“每个学校各自出题”。同样是考数据结构算法题,A学校可能出三道手写代码,B学校可能出五个算法填空,C学校甚至直接从教材习题里挑原题。刷多了893真题会发现,自命题普遍比408更贴近教材,也更容易出现“直接背模板就能写”的题。
这里有两个常见误区:一是认为自命题比408简单,随便刷刷就行;二是把LeetCode的中等难题刷遍了,回来自命题简单题却写不顺。真实的893命题有两个明显特点:第一,题目描述通常很长,会给你完整的结构体定义和函数签名,实际上是把代码框架都搭好了,你要填的是核心逻辑;第二,它允许的复杂度往往比408宽松,很多题O(n^2)就能拿满分,前提是你必须老老实实把代码写完整。
| 对比维度 | 408统考 | 893自命题 |
|---|---|---|
| 题目来源 | 改编经典算法,偏应用场景 | 教材习题原题居多,偏基础 |
| 代码量 | 一道大题15分,代码量中等 | 3~5道小题,单题代码量小 |
| 复杂度要求 | 严格,O(n)/O(n log n) | 相对宽松,O(n^2)常可接受 |
| 判分重点 | 思路+复杂度+边界 | 代码完整度+能否运行 |
| 复习策略 | 练变体题 | 背教材代码模板 |
这张对比表是我复习时自己整理的,后来对照这份总结的目录发现,它对两种考试的处理方式不一样:通用题型会标注“408/893均适用”,偏教材原型的题会单独标“893常考”,偏场景改编的题标“408常考”。这个细节帮我省了很多时间,因为自命题复习到后期,我基本只挑“893常考”的页面过,408的大题再单独练变体。
2.3 36页总结怎么用:一轮复习和冲刺阶段的不同打开方式
同样是36页,一轮复习和考前两周的用法完全不同。我第一遍复习时,是把这个总结当题库用的:先看题目,不看答案,在草稿纸上写核心代码,再和答案对照。这个过程非常费时间,一页可能花掉四十分钟,但收获也是最大的,因为代码题只有自己写过一遍才算真的会了。
到了十月下旬,时间紧起来,我换了一种方式:每天早中晚各翻十页,只读“题干+复杂度”两行,然后在脑子里过一遍解题步骤,最后只看代码里的关键三行。这个阶段的目的不是写代码,而是保持对题型的敏感度。真正的冲刺阶段,我反而把总结放回抽屉,改成用真题模拟考场,每套真题做完之后再翻总结,看自己哪一类题失分,再回到对应页面去补。
| 阶段 | 时间建议 | 打开方式 | 目标 |
|---|---|---|---|
| 一轮复习 | 9~10月 | 先做题再看答案 | 建立手写能力 |
| 强化阶段 | 10~11月 | 只看题干想思路 | 训练题型识别 |
| 冲刺阶段 | 考前两周 | 真题为主+总结补漏 | 保持手感和节奏 |
我特别想提醒的一点是:不要把这份总结当成“背多分”资料从头背到尾。它36页,按题型压缩过,但仍然需要你动笔。我在第二轮曾经偷懒只看答案,结果模拟考时连单链表反转都写得磕磕绊绊。从那以后我给自己定了个规矩:看一页总结,至少要在白纸上写十行代码才能翻下一页,不管代码是不是和答案一致。
3. 字符串与数组高频题复现:从暴力枚举到KMP
3.1 暴力枚举不是笨办法:先写对然后再优化
考场上最容易出现的情况是:拿到题就想最优解,想了十分钟没思路,最后连暴力解也没时间写。暴力枚举在算法题里听起来不高级,但它是手写代码的基本功。一个不超时的暴力解,配合清晰的注释,在408里至少能拿六成分数,在893自命题里甚至能拿满分,因为很多自命题根本没限制复杂度。
以LeetCode上最经典的“两数之和”为例,题目给一个数组和一个目标值,要求返回两个下标。很多人的第一反应是哈希表,但考场上如果一时间想不起哈希表怎么写,暴力枚举完全够用:
def two_sum(nums, target): # 暴力枚举:固定一个数,往后找补数 n = len(nums) for i in range(n): for j in range(i + 1, n): if nums[i] + nums[j] == target: return [i, j] return []这段代码的逻辑很简单:外层循环固定i,内层循环从i+1开始扫,避免同一对元素被重复统计。时间复杂度O(n^2),空间复杂度O(1)。两个参数里,nums是输入数组,target是目标值,返回的是下标列表。注意内层循环起点是i+1而不是0,这是最容易写错的地方,写成0会多算很多无效pair,极端情况下会出现自身加自身等于target的错误。
如果题目要求O(n),再往哈希表版本升级:
def two_sum_hash(nums, target): seen = {} # 值 -> 下标的映射 for i, x in enumerate(nums): need = target - x if need in seen: return [seen[need], i] seen[x] = i return []这个思路的关键是“边查边存”:当前元素查需要的补数是否已经出现过,再把当前元素存进哈希表。这样每个元素只扫一遍,时空复杂度都是O(n)。考研答题时建议把哈希表版本的注释也写上,尤其是“need = target - x”这一步,阅卷人一眼就能看出你思路清楚。
我在复习中发现的规律是:数组类算法题,暴力解往往是理解题意的第一层,最优解是第二层。资料里数组部分的题目基本都给了两层答案,我先抄一遍暴力解,再抄一遍优化解,对比两个版本的差异,这样比直接背最优解要牢得多。
3.2 KMP的核心:next数组和退化场景
KMP是字符串匹配里的高频考点,408和893都可能直接考“写出next数组”或者“用KMP算法求匹配位置”。很多复习到KMP就放弃的人,其实是死在了next数组的求法上,因为不同教材对next的定义不一样,有的从0开始,有的从1开始,有的用-1,手写的时候一混就全错了。
我用的版本是考研最常用的next数组定义:next[0] = -1,-1表示主串指针也要后移;next[1] = 0,因为第一个字符失配时没有前缀可以回退。求next的代码写下来是这样:
// p: 模式串, next: 长度为模式串长度的数组 void get_next(char *p, int *next) { int i = 0, j = -1; next[0] = -1; while (p[i] != '\0') { if (j == -1 || p[i] == p[j]) { i++; j++; next[i] = j; // 当前前缀长度就是i失配时要回退的位置 } else { j = next[j]; // 不匹配,回退到更短的前缀 } } }这里最核心的是else分支里的“j = next[j]”。很多初学的人在这个位置写成了“j = 0”,表面上看也能跑通部分用例,但遇到真回退时会漏掉已有匹配。i是当前正在比较的主串位置,j是模式串中已匹配的长度。每轮循环要么i和j同时前进,要么j回退到next数组指向前的位置,这样保证i不回头,整个匹配过程线性。
理解完next数组,KMP匹配本身反而简单:主串和模式串一起走,失配时模式串跳到next[j],主串不回溯。408一般不要求写完整的KMP匹配函数,更多是给你一个模式串让你手算next数组,或者给你next数组让你分析匹配过程。我在资料里看到KMP这部分时特别注意了它给的“退化场景”:如果模式串全是同一个字符,比如“aaaa”,next数组是递增的,但匹配时的比较次数依然不会退回O(mn),这就是KMP的价值所在。
3.3 双端队列与单调队列:小众但能救场
双端队列在数据结构教材里是一个线性表考点,但代码题里它经常以“单调队列”的形式出现。最经典的场景是滑动窗口最大值:给一个数组和一个窗口大小k,求窗口从左滑到右每个位置的最大值。暴力做法是每个窗口扫一遍,时间复杂度O(nk);用双端队列可以压到O(n)。
from collections import deque def max_sliding_window(nums, k): q = deque() # 存下标,下标对应的值从队头到队尾递减 res = [] for i, x in enumerate(nums): if q and q[0] <= i - k: q.popleft() # 队头下标已经滑出窗口,直接移除 while q and nums[q[-1]] <= x: q.pop() # 队尾元素不大于x,它再也不会成为最大值 q.append(i) # 当前下标入队 if i >= k - 1: res.append(nums[q[0]]) # 队头就是当前窗口最大值 return res这段代码的注释已经把每个分支都说明了。队头存的是窗口内最大值的下标,队尾存的是有可能成为最大值的候选下标。第一次写这个题的人,通常会在“队尾弹出”这一步犹豫,拿不定该弹出小于还是小于等于当前值的元素。我一般建议弹出“不大于当前值”的所有下标,也就是用<=,这样重复元素也能正确处理。
在自命题考试里,如果考纲里出现了双端队列,滑动窗口最大值几乎就是必背模板。即使是408没考过这个场景,它也是一种通用思路:用双端队列维护一个单调序列,可以解决很多“找区间最值”的问题,比如求每个长度为k的子数组最小值、求滑动窗口中的中位数等等。把这些场景在总结里记在一起,比孤立记“双端队列”四个字有用得多。
4. 树与图算法题怎么练:排序、遍历与递归返回值的取舍
4.1 冒泡排序与堆排序:408爱考过程,893爱考代码
排序是考研数据结构算法题里性价比最高的一块,因为它既容易出大题,也容易出选择题。408和893对排序的考法有明显差异:408喜欢考“过程”,比如给你一个初始序列,让你写出冒泡排序第一趟和第二趟之后的结果,或者问比较次数;893则更喜欢直接让你写出完整排序函数,甚至给你结构体数组,按成绩字段排序。
以冒泡排序为例,标准代码几乎每个考场都会用到:
def bubble_sort(a): n = len(a) for i in range(n - 1): swapped = False for j in range(n - 1 - i): if a[j] > a[j + 1]: a[j], a[j + 1] = a[j + 1], a[j] swapped = True if not swapped: break return a这里的swapped标志位是区分“标准冒泡”和“优化冒泡”的关键,它的作用是在某一趟没有任何交换发生时提前终止。对408的选择题来说,这个标志位会影响“最少趟数”的结论;对893的手写题来说,写上它能体现你对内层循环边界的理解。注意内层循环的范围是n-1-i,因为每一趟结束后,最大的i+1个元素已经沉到底部,不需要再参与比较。
堆排序在考研代码题里出现的频率更高,因为它的过程更复杂,适合出成“手写建堆”或“手写调整”。核心是堆调整函数:
def heapify(a, n, i): largest = i left = 2 * i + 1 right = 2 * i + 2 if left < n and a[left] > a[largest]: largest = left if right < n and a[right] > a[largest]: largest = right if largest != i: a[i], a[largest] = a[largest], a[i] heapify(a, n, largest) def heap_sort(a): n = len(a) for i in range(n // 2 - 1, -1, -1): heapify(a, n, i) # 从最后一个非叶子节点开始建堆 for i in range(n - 1, 0, -1): a[0], a[i] = a[i], a[0] # 堆顶与末尾交换 heapify(a, i, 0) # 新的堆顶调整,堆大小减1 return a我写这段代码时最容易错的是“建堆循环的范围”。n//2-1是最后一个非叶子结点的下标,从它开始往前逐个调整,才能保证整个数组成为大根堆;如果从0开始往下调,会出现局部有序但全局没排好的情况。交换a[0]和a[i]之后,堆的有效长度变成i,所以第二次heapify传的是i而不是n。堆排序时间复杂度稳定在O(n log n),空间复杂度O(1),但要注意它是不稳定排序,408选择题经常拿这个当考点。
4.2 图的遍历:邻接表与邻接矩阵的取舍
图的数据结构题在408里一般以应用题出现,在893里则以手写遍历居多。最常见的两个考点是深度优先搜索和广度优先搜索。实现方式取决于存储结构:邻接矩阵写起来最简单,查两点是否相邻是O(1),但遍历所有邻接点要扫一整行;邻接表写起来要处理指针或链表,但遍历时只访问实际存在的边,复杂度是O(V+E)。
| 存储结构 | 判断两顶点是否相邻 | 遍历顶点v的邻接点 | 适用场景 |
|---|---|---|---|
| 邻接矩阵 | O(1) | O(n) | 稠密图、选择题判断 |
| 邻接表 | O(degree(v)) | O(degree(v)) | 稀疏图、手写遍历代码 |
对代码题来说,我更推荐用邻接表,理由其实就一句话:代码量不大,复杂度又好写清楚。一个简单的DFS如下:
def dfs(adj, visited, u): # adj: 邻接表,visited: 布尔数组,u: 当前顶点编号 visited[u] = True print(u, end=" ") for v in adj[u]: if not visited[v]: dfs(adj, visited, v)这段代码的逻辑是教科书标准版,但有一个细节常被忽略:visited[u] = True必须放在递归调用之前,不能放在循环之后。如果你先遍历邻接点再标记当前点,同一层里会出现大量重复打印,甚至因为相互引用导致递归无法终止。递归结束后不需要还原visited,因为图遍历要求每个顶点只访问一次;只有求所有路径时才需要回溯还原,那是另一类题。
BFS的代码和DFS长得像,区别只是把系统栈换成显式队列:
from collections import deque def bfs(adj, visited, start): q = deque([start]) visited[start] = True while q: u = q.popleft() print(u, end=" ") for v in adj[u]: if not visited[v]: visited[v] = True q.append(v)这里最容易踩坑的是入队时就要立刻标记visited。如果你等出队时再标记,同一个节点会被多个邻居重复入队,在小规模数据上可能看不出问题,但在有环的图里会直接死循环。我把“标记”和“入队”视为一个原子操作,写BFS就不会乱。
4.3 树的递归:返回值设计决定成败
树的算法题是自命题的最爱,因为它代码短、逻辑清晰、适合手写。最常见的是求树的高度、判断平衡二叉树、求直径、最近公共祖先。这类题统一用递归解决,关键是设计好递归函数的“返回值”。我见过很多翻车代码,不是递归写不出来,而是返回值定义模糊,导致上层调用没法判断结果。
先看最简单的求二叉树高度:
def tree_height(root): if not root: return 0 left_h = tree_height(root.left) right_h = tree_height(root.right) return max(left_h, right_h) + 1这里的返回值定义很明确:返回以root为根的子树高度。空节点高度是0,叶子节点高度是1。递归过程是自底向上的:先问左子树多高,再问右子树多高,取较大值加1。这种设计不需要额外参数,也天然处理了只有左子树或只有右子树的非平衡情况。
如果题目升级成“判断是否是平衡二叉树”,就不能只返回高度了。我常用的做法是让递归函数返回两个信息——子树高度和是否平衡,用一个特殊值表示“不平衡”:
def is_balanced(root): def check(node): if not node: return 0 lh = check(node.left) if lh == -1: return -1 rh = check(node.right) if rh == -1: return -1 if abs(lh - rh) > 1: return -1 # -1 表示不满足平衡条件 return max(lh, rh) + 1 return check(root) != -1这个版本的返回值设计是:正常情况返回子树高度,一旦发现左右子树高度差超过1,立即返回-1,让上层递归剪枝。注意两个if的判断顺序:先检查左子树是否不平衡,再看右子树,这样能提前结束递归,避免做无意义的深度累加。如果只按“算高度再单独判断”的写法,每个节点会被重复访问多次,时间复杂度会从O(n)退化到O(n^2)。
树的递归题我总结出一个通用流程:第一步定义返回值的含义,第二步确定空节点返回值,第三步想清楚当前节点如何组合子节点的返回值,第四步写边界。36页总结里树的题目其实都是这四步,区别只在第3步的组合方式不同。
5. 算法题刷题避坑:五个高频翻车现场与修复方案
5.1 样例过了,交上去全错
现象:做模拟题或自命题机试时,题目给出的示例输入跑一遍完全正确,自己额外造几组数据就崩了。比如链表题,示例是1->2->3->4,删除倒数第2个节点,输出1->2->4,没问题;换成5个节点的链表,或者链表只有1个节点,程序直接报错或返回空。
原因:只按示例数据写代码,没有覆盖边界条件。考研手写代码虽然没有测试用例,阅卷时会按步骤给分,边界处理是评分表里明确的一档,缺失会整体降档。
解决:写任何题之前,先问自己三件事:输入为空怎么处理?只有一个元素怎么处理?重复元素怎么处理?把这三个分支在代码里显式写出来,即使不完整,阅卷人也能看到你在考虑边界。我后来给自己定的习惯是,每写一个函数,至少测三组数据:正常数据、最小数据、重复数据。
这里尤其要提链表类题目,单链表删除的代码很容易在只有一个节点时越界。我考前专门把“空链表、单节点、头节点被删”三种情况抄在总结的空白页上,每天看一遍,后来再遇到删除类题目基本能条件反射地补上判断。
5.2 KMP的next数组背了又忘,手写时死循环
现象:考场上写get_next,while循环里忘记让i前进,代码一跑就死循环,或者next数组结果跟标准答案对不上。
原因:next数组的标准代码依赖“j回退到next[j]”这个动作,死循环通常是因为把else分支的“j = next[j]”写成“j = 0”,在特定模式串下j会一直停在0,i永远不动。
解决:先不看代码,自己手推一次next数组再对照。推荐记两个锚点:next[0]=-1,next[1]=0。写代码时盯着else分支,不匹配就必须让j回退,回退不出去就把j=next[j]打印出来看,确认j在递减。这个坑我在模拟考翻过两次,后来每次写KMP都会先画一遍模式串的前后缀表,画完再写代码,基本不会再错。
如果你用的是从1开始的教材定义,那就整份资料都用那一套定义,千万别一套题里混用两种next。阅卷人按答案步骤给分,定义混用会导致后面的匹配过程完全对不上,丢分会非常可惜。
5.3 树的递归栈溢出或死循环
现象:代码看起来没问题,但遇到深度较大的树时程序崩溃,或者递归结束后输出重复结果。
原因:递归终止条件写错是最大隐患。常见错误是“只判空节点返回0,但不判当前节点是否为空”,导致空节点的left被访问;另一个错误是递归函数里重复调用自身但方向不对,比如求树高度时把左子树的递归写在右子树的返回值里,导致无限递归。
解决:树递归的终止条件必须在函数第一行,先判空再访问属性。如果发现死循环,优先检查是不是在递归入口就访问了node.left而没有判node本身为空。我自己的检查方法是把递归函数的第一行固定写成“if not node: return 0”,形成肌肉记忆,写树题就不再翻车。
更深一层的问题是“递归返回值类型”没想清楚。树的高度返回int,判断存在性返回bool,这两个东西不能用同一个模板硬套。我在资料上看到树的章节特意把两类题目分开排版,就是为了避免把返回值的语义搞混。
5.4 时间复杂度被扣分:只算最好情况
现象:自认为算法复杂度达标,结果被阅卷或面试官问住,因为写的代码在“最坏情况”下反而退化到O(n^2)。比如哈希表扩容分析,或者快速排序在基本有序时退化成O(n^2)。
原因:默认输入是随机的,没考虑极端输入;快排的哨兵取第一个元素,遇到降序数组每次只能排一个元素。
解决:分析复杂度时一律按最坏情况写出来,快排写“平均O(n log n),最坏O(n^2)”。408评分看重复杂度的分析过程,哪怕代码里用的堆排序,只要把复杂度边界写清楚,至少能拿步骤分。我在总结里看到快排那页旁边手写了一行“哨兵取中间位置”,就是用来提醒自己最坏情况的来源。
更实用的做法是给代码里的关键循环打标注。比如“for i in range(n): for j in range(i+1,n):” 直接在注释后写“O(n^2)”,这样一眼就能看到哪一段是复杂度瓶颈。阅卷人不需要你去解释,标注本身就是得分点。
5.5 盲目刷LeetCode,自命题反而写不顺
现象:LeetCode刷了几百题,模拟893自命题时,遇到课本原题却无从下手,或者写出来的答案不符合题目要求的函数签名。
原因:LeetCode强调的是最优解、异常输入、极端用例,所有代码都要在编译器里跑;考研手写代码更强调代码可读性、结构体定义匹配、注释清晰。两者评分标准不一样。
解决:分清两者的时间分配。我在冲刺期把LeetCode当成“锻炼思路”的工具,刷题时只看题解思路,不在编辑器里调一晚上;手写代码用真题和总结里的题目练,要求自己能在十五分钟内写完一题,不能改不能重跑。真正上考场时,代码是直接写在答题纸上的,没有编译器帮你检查,平时训练就应该闭卷写。
这里还有一个小技巧:893自命题如果给了结构体定义,答题时先原样抄一遍结构体,再写函数体。很多学校是按“结构体定义是否正确”给分的,抄错一个字段名就可能丢掉一整档分。我见过好几个同学在自命题考场上因为没写结构体,代码逻辑全对但只拿了一半分,非常冤。
6. 最后两周的收尾技巧:复杂度口算与答题模板化
6.1 一个数,快速判断你的算法能不能过
考研没有在线评测,但复杂度分析要写在答题纸上。我有个笨但实用的方法:先看题目是否给数据规模,给了就估算一个数量级,然后对照下面这张表,判断你的方案是否在安全线内。
| 数据规模n | 可用复杂度 | 典型算法 |
|---|---|---|
| n <= 10 | O(n!) | 全排列暴力 |
| n <= 20 | O(2^n) | 状态压缩、剪枝搜索 |
| n <= 10^3 | O(n^2) | 冒泡、暴力枚举 |
| n <= 10^5 | O(n log n) | 快排、堆排、KMP |
| n <= 10^7 | O(n) | 双指针、哈希表 |
更关键的是,408代码题一般不会给你特别大的n,它的目的是让你写出“复杂度正确”的算法,而不是真去跑数据。所以答题纸上写的复杂度分析,必须和你的代码实现完全对应。哪怕你实际用了O(n^2)的暴力解,你写“时间复杂度O(n^2)”,也比写了O(n)却实现得不对要好。
6.2 三个背下来就能救场的模板
最后两周我不再追求新题,只过三个高频模板:双指针判断回文、单链表反转、二叉树递归遍历。这三个模板覆盖了数组、链表、树三类最常见的代码题。
# 双指针判断回文,背下来的模板 left, right = 0, len(s) - 1 while left < right: if s[left] != s[right]: return False left += 1 right -= 1 return True# 单链表反转模板 def reverse_list(head): prev = None cur = head while cur: nxt = cur.next cur.next = prev prev = cur cur = nxt return prev每个模板都对应一类题型。双指针是“一对多”题型的基础,还能推广到有序数组的两数之和、删除重复元素;单链表反转是所有链表题的基础,很多链表大题的核心步骤就是在局部做反转;二叉树递归模板其实就是前面写的判空、递归左右孩子、组合返回值那三步。把这三个模板写在总结第1页,每天默写一遍,比刷十道LeetCode更稳。
6.3 考场上最后五分钟查什么
我模拟考时养成了一个习惯:代码写完后不急着做下一题,回头检查三个位置——递归的终止条件有没有写在第一行,循环的边界是小于还是小于等于,有没有返回空值。这三个位置占了代码题80%的低级错误。从那以后我每次模拟考都强制走一遍这三查,自命题考试的最后一道代码题,基本能在十分钟内干干净净写完。这份36页的总结不一定完美,但它帮我稳住了最不踏实的一块,希望帮到你。
本文还有配套的精品资源,点击获取