1. 开工前的整体思路:这门课到底在训练什么
3月8日,学期进度条过半。不少同学在这个时间点正被二叉树遍历和图的邻接矩阵按在地上摩擦。如果你也卡在某个结构上,别慌,这不是你智商的问题,而是这门课的节奏本身就容易让人产生“听懂了但不会写代码”的错觉。
数据结构这门课,表面上是教你一堆存储数据的容器,实际上它在训练两件核心能力:逻辑抽象能力和复杂度估算能力。前者让你看到现实问题时能画出数据之间的关系——一对一、一对多、多对多;后者让你在实现方案时能分辨“这个写法跑到数据集上会不会爆”。
3月8日这个时间节点,大概率是课程讲到树或图的阶段。线性表、栈、队列这些内容已经学过,考试里它们还会反复出现,因为它们是后续结构的构建基础。如果你现在还在用抄代码的方式过作业,我建议你先停一停,把思路放在“为什么这样设计”上。比如,栈为什么适合表达式求值?队列为什么适合任务调度?这些问题的答案写下来,比你背十道例题都管用。
这篇文章我不会带你从头背一遍教材目录,而是从实操者的角度拆解学期中段的数据结构学习重点:当前阶段真正该掌握什么,考试爱考什么,代码题容易在哪里翻车,以及后半学期怎么规划才不被动。内容主要结合我自己的踩坑经历和辅导某同学时的真实反馈,希望对你有参考价值。
2. 前半学期的知识底座:线性结构是树和图的地基
2.1 线性表、栈、队列:不要只背接口,要理解时机
3月8日回头看,前半学期的线性表、栈、队列其实是最容易被轻视的部分。很多人觉得链表就是搞个指针指来指去,栈和队列就是两个数组加几个操作,看起来很简单,但到学树的时候才意识到问题:树的递归遍历、广度优先遍历、表达式的转换,全部建立在顺序表和队列的熟练程度上。
以栈为例。它最经典的应用场景之一就是括号匹配。很多同学写这个代码时会犯一个典型错误:只判断左右括号数量是否相等,不考虑嵌套顺序。比如输入([)],左右括号数量确实是各两个,但这不是合法匹配。正确做法是遇到左括号就入栈,遇到右括号时检查栈顶是否为对应的左括号,是则弹出,否则直接判定失败。这个场景虽然简单,但它背后体现了栈“后进先出”的核心特性,而在树的非递归遍历中,你需要用同一个特性去模拟函数调用栈的行为。
队列同样是容易低估的内容。循环队列的判满问题——区分“队空”和“队满”——本质上是牺牲一个存储单元来换取判断的明确性,这种做法你在树的层序遍历中会看到相似的影子:一个数组或链表如何在一个节点出队后,正确地把它的孩子节点入队,这需要你对队首队尾指针的变动有清晰的感知。
我的建议是:不要只记住“栈是后进先出、队列是先进先出”这句话,而是自己写一次基于链表的栈实现,再写一次基于数组的循环队列实现。手工模拟过程中你会理解那些边界条件是怎么来的,比如队满条件(rear + 1) % maxSize == front。这个过程虽然看起来费时间,但它会直接帮你后面理解递归、遍历和图算法里的状态管理。
2.2 递归思维:学数据结构的第一个分水岭
如果说线性结构还有套路可循,那么递归就是很多人的第一个分水岭。3月8日前后,递归的理解深度基本预示了后面树和图的掌握程度。
递归的本质是“函数自己调用自己,每次调用都带着更小的子问题,直到触达递归出口”。很多教材都会用阶乘和斐波那契数列来举例,这两个例子当然好懂,但它们不够典型。真正应该好好练的是递归遍历数组和递归处理链表,尤其是逆序输出链表元素这类问题——每一层递归先对下一层调用,再打印当前节点,天然利用函数调用栈实现了逆序。
另外一个重要的点是递归出口的位置。写递归一定要先想清楚什么时候停止,否则就是无限递归。以二叉树的遍历为例,出口是“当前节点为空”,这个出口几乎出现在所有树的递归模板里。如果你写递归时不先写出口,后面调试起来会非常痛苦,而且很容易在递归层数深的时候直接栈溢出。
递归还有一个容易踩的坑,就是递归过程中的返回值设计。有些人会把递归函数写成了“每一步都返回一个值,但调用处根本没有使用这个返回值”,导致逻辑看似正确但结果错误。排查这类问题最好的方式是画递归调用树,把每次调用的输入输出手写下来。虽然看起来很笨,但确实有效。
我自己带过一位同学,他递归一直学得不太好,后来我让他做一件事:不做代码题,只做“写在纸上的递归模拟”,像调试器一样把自己当作计算机,一步步执行递归调用。这个方法坚持了两周后,他的树相关代码能力提升非常明显。说明递归并不是靠天赋,而是靠“头脑模拟执行”的练习量。
3. 二叉树:为什么这是全课程的核心枢纽
3.1 二叉树的遍历框架:一套模板吃遍天
3月8日前后,二叉树往往是重头戏。它不只是为了考几道树的题目,更重要的是,二叉树的遍历框架是整个数据结构课程的核心枢纽。很多看起来毫不相关的问题,翻译成“在某次遍历的某个时机做某件事”,就迎刃而解。
先记住最基础的三件套:
- 前序遍历:先处理当前节点,再递归左子树,再递归右子树。
- 中序遍历:先递归左子树,处理当前节点,再递归右子树。
- 后序遍历:先递归左子树,再递归右子树,最后处理当前节点。
代码模板都长一个样,区别只是“处理当前节点”这个动作的位置。很多初学者看到三种顺序就头皮发麻,其实理解它们最好的方式是手动模拟一棵只有三个节点的小树,把访问顺序一步步画出来。你会理解为什么中序遍历在二叉搜索树中会输出有序序列,为什么后序遍历适合做树的删除操作(因为要先处理孩子再处理自己)。
层序遍历是另一块必考内容。它用的是队列:先把根节点入队,然后循环,每轮出队一个节点,处理它,再把它左右孩子入队。这个过程符合“先进先出”的节奏,和你排队买票的场景一样。很多人在层序遍历里忘记考虑空节点,导致代码报错,这里尤其要注意:入队前要判断孩子节点是否为空。
3.2 高度、深度、叶子节点统计:递归返回值的设计
二叉树题目里最经典的几道“小题”,往往是高度计算、节点统计、叶子节点计数。这些题看似简单,却完美考察你对递归返回值设计是否清晰。
以树的高度为例,递归思路是:如果当前节点为空,返回0;否则分别获取左子树高度和右子树高度,取两者较大值再加1。为什么加1?因为当前节点自己也算一层。这个“加1”的逻辑就是递归返回值的核心——每一层递归向上传递时,都要包含“当前层的贡献”。
很多同学在这类题目上翻车,不是因为递归不会写,而是没有区分“全局变量统计”和“递归返回值统计”。比如统计叶子节点数量,用全局变量加递归遍历当然能实现,但如果你在递归函数里同时修改全局变量又返回值,代码很容易混乱。我的建议是:统计类题目优先考虑“用返回值带回结果”,这样函数职责单一,调试也方便。但要注意,有些题目(比如求二叉树的最大路径和)必须在递归过程中同时更新全局最大值,这时候返回值表达的是“从当前节点向下延伸的最优值”,而全局变量记录的是“整棵树中的最优路径值”,两者目的不同,不要混在一起。
3.3 树的构建与还原:理解前序、中序的配合价值
另一个高频考点是根据遍历序列还原二叉树。最常见的形式是给出前序遍历和中序遍历序列,让你还原整棵树。这个题目的思路值得好好理解,因为它能帮你加深对“什么是前序、什么是中序”的理解。
思路的核心是:前序遍历的第一个节点是根节点;在中序遍历中找到这个根节点的位置,它左边是左子树的中序序列,右边是右子树的中序序列;然后根据左右子树的节点个数,回到前序序列中,把前序序列也切成对应的左右子树部分;接着递归处理左右子树。
这个过程不难,但实现时很容易在下标计算上出错。我的建议是:先不要直接写代码,拿笔在纸上建一棵简单树,把前序和中序列出来,然后把“切分点在哪里、左右子树的长度是多少”写清楚。只要这个手推过程明白了,代码就是照搬而已。特别注意递归函数里表示序列范围的下标,建议用“左闭右开”区间,可以避免一堆+1、-1的边界错误。
4. 图结构入门:从邻接矩阵到遍历算法
4.1 图的存储选型:矩阵还是邻接表?这是个选择题
3月8日之后,课程往往会进入图。图结构的难点在于存储方式灵活,不同场景下选错了存储方式,代码写起来痛苦程度完全不同。
邻接矩阵的优点是判断任意两点是否相邻非常快,复杂度是O(1),适合稠密图。缺点是需要n * n的空间,当图的顶点数上万时,空间会非常夸张。邻接表的优点是只存实际存在的边,适合稀疏图,但判断两点相邻时需要遍历链表,复杂度是O(degree)。以我见过的考试和实际开发场景来说,多数题目给的图都是稀疏图,邻接表更常用。
有一个实操细节很多人忽略:用邻接表存储无向图时,添加一条边需要在两个顶点的链表里各插入一次。如果漏了反向边,遍历时会出现“有的节点访问不到”的诡异现象,排查起来很恼火。我当时排查这个问题时花了一个多小时,最后发现只是建图时漏了一行addEdge(b, a)。这种坑每一位初学者大概率都会踩一次,踩过之后记得在写建图函数时先做好注释,写完立刻检查对称性。
4.2 深度优先搜索和广度优先搜索:仅仅是“遍历”而已吗
图的深度优先搜索(DFS)和广度优先搜索(BFS)是后续所有图算法的基础。理解它们的关键,在于理解搜索树这个概念——从某个起点出发,DFS和BFS都会扩展出一棵“访问树”,这棵树不是原图,而是“按某种策略访问到的节点和边”的集合。
DFS用递归或显式栈实现,一条路走到黑,撞了南墙再回头,适合做连通性判断、环检测、路径查找。BFS用队列实现,一圈一圈向外扩展,找到的路径天然是最短路径的步数(在无权图或边权为1的图中),适合做最短路径的简化版本或者层级扩散类问题。
这两者在代码上的差别,只是把栈(或递归)换成了队列。如果你已经熟练掌握了树的层序遍历,那么BFS几乎不需要新学,只是从树的孩子列表变成了图的neighbors数组。
不过图的遍历里有一个非常重要的点:visited数组标记的时机。以BFS为例,很多人的做法是“出队时才标记已访问”,这会导致同一个节点被多次入队,严重时可能死循环。正确做法是“入队时就将节点标记为已访问”。原因很简单,队列里的节点已经“承诺”要处理它了,如果不立刻标记,另一个相邻节点可能再次把它入队。这个问题,我在现场辅导时看过太多版本了,属于排名前三的经典错误。
4.3 最短路径与拓扑排序:从模板到理解
图算法里,最短路径和拓扑排序是两道绕不开的坎。狄克斯特拉(Dijkstra)算法解决的是非负权图的最短路径问题,核心思路是贪心:每次从未确定最短距离的节点里选一个距离最小的,更新它的邻居。它可以用暴力循环找最小值,也可以用优先队列优化,后者才是实际代码更常用的方式。
这个算法初学者最容易误解的地方在于“为什么贪心在这里是成立的”。简单说,因为在非负权图里,当前离源点最近的节点,其最短路径已经不可能再被别的节点“绕道更新”变得更短了,所以可以放心地把它“定下来”。你可以把想象成“抢椅子”——每一轮都有一个节点确定最终距离,确定后就不再参与后面的比较。
拓扑排序则是针对有向无环图(DAG)的线性排序,核心操作是不断找入度为0的节点,把它加入排序结果,再删除它的出边,更新邻居的入度。这个算法看着简单,但对“有向无环图”这个前提的敏感性要求很高。如果图里有环,排序过程中会出现“找不到入度为0的节点”的情况,也就是剩余节点数大于0但队列已经空了,这时就可以判定图中有环。很多关于任务调度、课程先修关系的题目,都会用到这种“判断环是否存在”的技巧。
我在初学拓扑排序时犯过一个典型错误:只做了一次“删除入度为0节点”的操作,没有用循环反复做,导致结果只包含第一个节点。实际上,整个流程可以放在一个队列里循环处理,每当一个节点出队,就去更新邻居的入度,邻居入度变为0则入队,直到队列为空。理解了这个循环结构,拓扑排序就不会再写错了。
5. 排序与查找:考试占比高,但更要练“直觉”
5.1 常见排序的复杂度与稳定性,不只是背表格
排序算法在数据结构课程里永远占有一席之地。3月8日前后的复习,一般都会涉及直接插入排序、冒泡排序、简单选择排序、快速排序、归并排序、堆排序等。关于不同排序的稳定性、平均时间复杂度和最坏时间复杂度,几乎每个学校都会考一张对照表,但仅仅背表不够,你得能“推导”出这些结果。
比如快速排序,平均O(n log n),最坏O(n²),最坏情况出现在每次分区都极度不均衡时,比如对已经有序的数组使用固定基准点做快排。理解了“为什么”最坏会变成O(n²),你在实际写代码时就会下意识地考虑用三数取中法(取首、中、尾元素的中值作为基准)来降低最坏情况出现的概率。这种意识,不是背表格能获得的。
再比如归并排序,它总是O(n log n),因为每次分区都严格二分,且合并时不管数据顺序如何都要线性扫描一次。代价是需要额外的O(n)辅助空间。实际考试里常问的一个变体是“在链表的场景下实现归并排序”,这时你可以利用链表的天然特性做到不用额外数组,只需用快慢指针找到中点,然后递归合并两条有序链表。这类题目如果能自己想通,说明你对递归和指针操作的掌握已经进入另一个层次了。
5.2 哈希查找:空间换时间的最佳体现
查找这一块,顺序查找、二分查找概念相对简单,真正值得投入时间的是哈希查找。哈希表在后续很多高阶场景中都会用到,比如缓存淘汰、去重、频率统计等。
哈希表的核心是哈希函数和冲突处理。考试中常考的两种冲突处理方式是开放定址法和链地址法。开放定址法的关键是探查序列,了解线性探测((h(key) + i) % tableSize)和平方探测((h(key) + i²) % tableSize)各自的优缺点——前者容易产生聚集现象,后者可以降低聚集但不容易保证所有位置都能被探查到。链地址法就直观很多,每个槽位挂一条链表,冲突的键都往链表后面挂,查找、插入、删除的操作都围绕这条链表展开。
实际考试里,哈希表题目往往还要求计算平均查找长度。做这种题,画表是唯一靠谱的方法。你先把每个键插入的位置手工算一遍,再把每个键的查找长度列出来,最后算平均数。这个过程非常机械,但你需要特别小心的是“删除过元素后的查找”问题,使用开放定址法时,删除元素通常会用一个特殊标记(比如墓碑标记),不能直接置空,否则会影响后续元素的查找路径。这个细节已经是老生常谈,但每次考试依然有人踩坑。
5.3 排序稳定性:概念很抽象,例子最直观
“排序稳定性”是一个让很多同学困惑的概念。稳定排序指:如果两个元素的键值相等,排序后它们的相对顺序不发生变化。比如按成绩排序,先按学号排好序,再按成绩做稳定排序,最后成绩相同的人依然保持学号顺序。
直接插入排序和冒泡排序是稳定的;简单选择排序不稳定,因为它会“跳着”交换元素,可能把相等元素的相对顺序改变;快速排序的“不稳定”体现在分区交换过程中,相等的元素可能被交换到另一个相对位置。归并排序是稳定的,前提是合并时遇到相等元素优先取左边序列的元素。堆排序不稳定,因为调整堆的过程会把后面元素往前放。
如果觉得抽象,举一个经典例子:按分数排序学生,分数相同的人要保持姓氏的字母顺序。如果用了不稳定排序,最终结果里同分学生的姓氏顺序就乱了,这在很多排序类应用中是致命问题。理解了场景,就不容易搞混了。
6. 这个阶段最容易踩的五个坑
6.1 递归爆栈与死循环
递归爆栈的问题在树和图的深度优先搜索里非常常见。树的递归深度等于树高,如果树高度极大,比如退化成一条链,递归深度就是节点数,很容易击穿系统栈。要缓解这个问题,最直接的方法是改写成显式栈的迭代版本。比如用栈模拟前序遍历:先把根节点入栈,循环时弹出节点处理,然后先把右孩子入栈,再把左孩子入栈,因为栈是后进先出,这样左孩子会先被弹出。
图DFS里的死循环,几乎都源于缺少visited标记或标记时机不对。我在辅导时见到的典型错误是:递归进下一个节点后再把当前节点标记为已访问,导致上一层的递归回来后又重新访问当前节点。这类问题排查起来,最快的办法是在递归入口处打印当前节点编号,一步步看出来回跳转的顺序,规模一缩小,原因就清楚了。
6.2 指针和引用的边界处理
链表相关的题目里,空指针是永恒的主题。常见错误包括:对空指针解引用、删除节点时没有保存下一个节点的地址、在头节点操作时没有更新头指针。处理方式说简单也简单,但需要形成肌肉记忆:任何用到指针的地方,先想清楚它是否可能为空;任何修改链表的操作,先把要变动的指针的“原始状态”画出来。
举个例子,在单链表头部插入节点:先让新节点的next指向原来的头节点,再更新头指针指向新节点。这两步不能反,如果先更新头指针,原来的头节点地址就丢了。很多同学写链表题时习惯“想到了就写”,结果把顺序搞反,调试时看代码也看不出问题,最后只能从头推演一遍才明白。链表的题,肉眼检查代码常常没用,最好是手动画出每一步指针指向关系。
6.3 循环队列判满的混乱
循环队列的判满逻辑,很多教材用“牺牲一个单元”的方案,即(rear + 1) % maxSize == front时判定队满。这个方案的好处是留出一个空位来区分队空和队满。如果你用的是“计数变量记录元素个数”的方案,判空判满条件就是count == 0和count == maxSize,逻辑简单很多,但要记得在入队和出队时同步更新count。
复习时建议把两种方案都写一遍,因为不同学校甚至不同教材默认的方案不一样,考试时最好先看清楚题目里是哪种定义,不要凭惯性做题。我当时就吃过这个亏:考试题目里定义了计数变量方案,我惯性用牺牲单元方案,结果判断满的条件写错,白白丢分。
6.4 排序算法代码边界错误
以快速排序为例,分区的循环很容易写出死循环或漏判边界。常见错误是双指针扫描时没有加i < j条件,导致指针越界。再比如循环里用<=还是<,这个细节直接决定了最终分区的正确性。我建议写快排时把分区过程单独抽成函数,并在每个指针移动后检查是否符合预期。
还有一个非常常见的错误:递归调用的范围写错。比如分区返回的基准点下标是pivot,那么递归排序的分区范围应该是[low, pivot - 1]和[pivot + 1, high],不能把pivot本身也包含进去。这个错误在调试时极具迷惑性,因为结果看上去像是“大部分排序正确,但中间有几个数位置不对”。这是典型的边界范围写错问题,排查时用一个小数组手动模拟一轮分区就能定位。
6.5 时间复杂度的误判
“O(n log n)就是最好”的想法,其实过于绝对。如果数据规模很小,比如只有几十个元素,O(n²)的插入排序可能反而更快,因为它没有额外的常数开销和递归调用成本。很多算法教材讲排序时会给出“当数据量大时才用快排,数据量小时插入排序更好”的说法,这是工程实践里真实存在的“切换阈值”,不是纯理论空谈。
考试里还有一种误判来自对空间复杂度的忽视。归并排序的O(n)辅助空间、快速排序的O(log n)递归栈空间,这些细节常常被题目挖坑。复习时建议把每个排序算法的“额外空间复杂度”单独列一列,和一些数字对比记忆,防止考试时凭感觉乱写。
7. 后半学期怎么规划才不慌
7.1 以“做题”检验理解,不以“看懂”为标准
3月8日之后到期末,中间通常还有几周时间。我见过太多同学的复习方式是“把教材从头读一遍,觉得都会了”,可一上机写代码就卡住。正确的方式应该是以“做题”为检验标准。你觉得自己掌握了栈,就写一道括号匹配的题,再写一道基于栈的表达式求值;你觉得理解了BFS,就做一道“迷宫中找最短路径”的题。只有在代码里完成输出的那一刻,才说明你真的掌握了。
做题时不要只看题解,真正有效的方法是:先自己想思路,写第一版代码,跑测试数据,错了就debug,实在卡住了再看题解。看题解后不是复制一遍就结束,而是把题解关掉,凭记忆重写一遍。这个过程虽然慢,但每一步都是在加固“脑子里的知识到手上代码”的转换链路。
7.2 强烈建议:建一个“错误题库”
我自己的习惯是准备一个文档,记录每一道做错的题目的:错误原因、错误代码片段、正确思路、以及以后写同类题时的注意点。听起来有点麻烦,但实际效果极好。尤其是“同一类错误反复犯”的情况,把错误集中起来看,你会很快发现自己的薄弱点往往集中在少数几个模式上,比如边界处理、初始值设置、遍历范围等。
有些同学喜欢在代码文件里写注释记录错误,这也行,但不如单独放在一个可以回看的文档里。考前复习时,不要从头看教材,直接看自己的错误题库,效率至少是看书的两倍。我辅导某同学时,他把期中前所有做错的题整理成了一个文档,考前看了一遍,期末相关试题正确率提升非常明显。
7.3 手写代码与上机,两种能力都要练
上机写代码注重编译运行和调试,而考试手写代码完全没有这些工具,更考验你在纸上组织逻辑的能力。这两者差异确实存在,需要分开练习。平时上机时,可以刻意先在纸上写出完整代码框架,再写到机器里运行。这样做一开始会感觉慢,但几周后会形成“先想清楚再敲键盘”的习惯,手写题的正确率会整体上升。
手写代码还有一个练习技巧是“口头走查”:写完后,自己当计算机,拿一个小测试用例一步步跑一遍代码,用手模拟变量的变化。这个过程能帮你提前发现很多低级错误,比如数组越界、判断条件写反、返回值漏写等。我遇到考试中时间紧张时,规定自己前几分钟必须先把大框架写完,再花时间补细节,避免最后一刻还在改bug。
8. 一些实用工具与学习方法
8.1 调试工具的使用,别只会print
数据结构课程的代码调试,很多人还是习惯到处加print输出中间变量。这种办法不能说不行,但效率很低。如果课程用的语言支持调试器,比如C/C++的GDB,或者VS Code可视化调试、IDE断点,建议花一点时间学习基本的单步执行、查看调用栈和监视变量值。对于递归代码尤其有用,因为你能直接看到每一层递归的参数。
我给你一个替代办法:如果编辑器支持断点调试,试着在递归函数入口加一个断点,然后单步走几步,看参数是如何一层层变化的。你很快会理解递归调用的栈过程,比单纯在纸上模拟更直观。这个习惯一旦建立,后面学图的DFS递归版本时会轻松很多。
8.2 画图是数据结构学习的“元技能”
数据结构是抽象的,但画图能让它具体。不论你是用白纸画链表节点变化,还是用工具画树和图的遍历过程,都能提升理解速度。有一个很简单的练习:把一棵二叉树的前中后序遍历顺序分别画出来,然后对比三种遍历在处理同一个节点时的时机差异。画过一遍之后,很多概念题目都难不倒你。
图的画法上,可以尝试用“箭头标记访问顺序”的方式模拟DFS和BFS的搜索过程,这比直接背代码有用得多。我还见过有同学把图的遍历过程用颜色标记节点状态:白色未访问、灰色已入队或已入栈、黑色已处理完毕。这个三层状态的标记方法对理解“为什么入队时就标记visited”非常有帮助,强烈推荐试一试。
8.3 代码仓库与笔记:为期末复习蓄力
学期过半,重新组织一次学习笔记是值得的。整理时不要抄书,而是用自己的话写“这个结构是什么、解决什么问题、典型代码模板、常见错误点”。如果时间有限,至少整理树和图这两块,因为它们是后半学期的重点,也是考试大题的高发区。
笔记里建议包含几个固定的“模板”:二叉树遍历模板、BFS模板、DFS模板、Dijkstra模板、拓扑排序模板。这些模板不是让你机械套用,而是作为你写代码的起点。实际做题时,你在这个起点上做修改,比从零开始写要快很多。我考前会把所有模板代码重新敲一遍,不是为了背,是为了在敲的时候复盘每个模板的边界条件是怎么处理的。
9. 从3月8日开始的行动清单
学期中段是一个很适合“重启”的节点。如果你前半学期学得比较吃力,此时重新整理学习节奏,期末翻盘的可能性仍然很大。我当时见过不少在树这里豁然开朗的同学,原因都是不再硬啃书,而是开始大量手写代码和画图。下面这份行动清单供你参考:
- 把线性表、栈、队列的代码手写一遍,重点检查边界条件。直接用题目验证,不靠感觉。
- 递归练习每天至少2道题,题目从“反转链表”“合并有序链表”“二叉树的最大深度”这类基础题开始。
- 二叉树遍历框架必须做到“闭着眼睛能写”,并把前中后序和层序遍历都整理成自己的模板。
- 图的基础算法(BFS、DFS)手写3遍,要求不用看笔记,从空文件开始写出完整的可运行代码。
- 排序算法至少手写快排和归并,并清楚说出它们的时间复杂度来源和空间消耗。
- 建立错题库,把平时练习的错题整理进去,每周回看一次。
- 每周至少做一次“无编辑器手写代码”训练,训练考试状态。
这份清单不是让你一口气完成,而是作为后面几周的学习路线。每完成一个节点,你对数据结构的整体掌控都会更扎实一些。
我个人在实际辅导中得到的感受是:数据结构并不考验天赋,考验的是你愿不愿意花时间把抽象的问题画成具体的样子,把代码的每一步都想清楚。认真做到这一点的人,无论考试还是后续课程里的算法设计,都会顺畅很多。