乐于分享
好东西不私藏

【附代码题核心模版】408 数据结构代码题到底怎么学?我翻了近 5 年经验贴,发现很多人一开始就练错了

【附代码题核心模版】408 数据结构代码题到底怎么学?我翻了近 5 年经验贴,发现很多人一开始就练错了

我整了一份《408 数据结构核心代码模板整理》并做成了 PDF,方便大家保存、打印,或者在暑假复习时随时查阅。

(原创且免费公开)公众号后台回复:代码题

领取《408 数据结构核心代码模板整理》PDF

每年复习到数据结构代码题,总会出现两种极端。

一种是看到指针、链表、递归就头疼,干脆安慰自己: “代码题就十几分,不会也不影响上岸。”

另一种是刚学完线性表,就跑去力扣刷题,一天研究两三道中等题。折腾半个月,回头一看,王道书还停在第二章。

这两种学法都不太对。

我集中看了近 5 年关于 408 数据结构代码题的经验贴、讨论帖和部分评论区,发现真正考得不错的人,方法不一定完全相同,但有一个共同点:

他们没有把 408 代码题当成程序设计竞赛,而是把它当成一道“需要写出算法过程的专业课大题”。

这两个东西看起来很像,准备方式却差得很远。


一、先弄清楚:408 代码题到底在考什么

408 的算法题通常不是让你写一个能提交、能编译、能通过所有测试点的完整程序。

一般不需要写输入输出不需要处理工程环境,也不要求把头文件、主函数、内存释放全补齐

真正需要写的,往往是:

  • 一个核心函数;
  • 一段算法思想说明;
  • 时间复杂度分析;
  • 空间复杂度分析。

这也是为什么很多上岸经验都会强调:

408 需要的是“能把思路翻译成相对规范的 C 或 C++ 代码”,而不是熟练掌握各种竞赛技巧。

基础较弱的同学,至少要补到下面这个程度:

  • 能看懂并写出循环、判断和数组操作;
  • 知道函数怎么传参;
  • 看得懂结构体;
  • 能处理链表指针;
  • 理解递归函数是怎么调用的。

再往后,才是数据结构本身。

如果连 p = p->next 为什么能让指针后移都不明白,就直接去背链表算法,最后大概率只会背下一串字符。

题目稍微换个问法,就不会了。

但也没必要先花一两个月,系统学完整本 C 语言。

对于只准备 408 初试的人来说,C 语言补到“够用”就可以了。

重点掌握:

  • 结构体;
  • 指针;
  • 数组;
  • 函数;
  • 递归。

至于文件操作、复杂语法和工程项目,暂时都可以往后放。


二、第一轮不要把大量时间耗在代码大题上

不少经验贴都提到,第一轮复习数据结构时,代码题可以学,但不适合死磕

原因很简单:

第一轮最重要的任务,是先建立数据结构的整体框架。

你需要先弄清楚:

  • 顺序表和链表有什么区别;
  • 栈和队列分别解决什么问题;
  • 树的遍历为什么经常使用递归;
  • 图的 BFS 和 DFS 分别在做什么;
  • 各种排序算法的过程和复杂度是什么。

这些东西没有建立起来,代码题几乎不可能真正学会。

有些同学第一轮就同时看教材、看视频、做辅导书,还给每道算法题抄一遍完整答案。

结果是数据结构一门课学了很久,其他三科的进度全部被拖慢。

失败经验里反复出现的一个问题,就是:

资料开得太多,笔记做得太细,产生了“每天都在学”的感觉,实际上有效输出很少。

比较合适的第一轮做法是:

  1. 看完一章;
  2. 挑最基础、最典型的代码过一遍;
  3. 先看懂算法过程;
  4. 再尝试脱离答案,写出大致框架。

写不完整没有关系,但至少要知道:

每一句代码在做什么,为什么要这样写。

比如学单链表,第一轮至少要会:

  • 遍历链表;
  • 查找结点;
  • 插入和删除;
  • 头插法、尾插法;
  • 链表逆置。

学二叉树,至少要理解:

  • 先序、中序、后序递归遍历;
  • 层序遍历为什么需要队列;
  • 如何统计结点数、高度或叶子结点数。

这些基本操作不只是“要背的模板”,更是后面真题进行组合和变形的零件。

第一轮的目标,不是看到一道陌生算法题就能马上写出最优解。

更现实的目标是:

看到答案时能够看懂,合上书后能够写出七八成。


三、真正有效的顺序,不是“看答案—背答案”,而是这四步

很多人学代码题的过程是这样的:

看题,不会。 看答案,觉得懂了。 抄一遍,感觉会了。 过几天再做,还是不会。

问题就出在“觉得懂了”。

看懂别人的代码,只代表你的阅读没有障碍,不代表你能独立产生这段代码。

我比较认同的练法,可以总结成四步。


第一步:先用人话说思路

先在脑子里想想思路,别急着写代码。

拿到题目后,先想清楚下面几个问题:

  • 数据放在哪里?
  • 我要遍历几次?
  • 需要几个指针或辅助变量?
  • 每一步到底在改什么?
  • 有哪些特殊情况需要处理?

比如题目要求:

删除单链表中所有值为 x 的结点。

不要一上来就背代码。

先用自己的话把过程说清楚:

  1. 从头开始遍历;
  2. 如果当前结点的下一个结点值为 x,就让当前结点跨过它;
  3. 删除后,当前指针不能立刻后移;
  4. 因为新的后继结点仍然可能等于 x
  5. 如果不等于 x,当前指针再向后移动。

当这几句话能顺利说出来时,代码其实已经完成了一半。

能把算法过程讲明白,才有可能把它写明白。

数据结构里很多题,都适合自己画图。

特别是:

  • 链表指针变化;
  • 树的递归过程;
  • 栈和队列的入栈、出栈;
  • 图的遍历顺序。

画图和手动模拟,通常比死记一段代码更接近真正的理解。


第二步:先写一个能做出来的解法

考场高压环境下,不要一开始就逼自己想到最优算法。

408 的题目虽然经常要求:

“时间上尽可能高效。”

但考场上最怕的,不是复杂度差一点,而是十分钟过去了,答题纸上还是空的。

练习时可以先写朴素解法,再考虑怎么优化。

例如:

  • 两层循环能解决,就先把两层循环写对;
  • 需要额外数组,就先写额外数组;
  • 需要多遍历一次,就先保证整体逻辑正确。

先确保算法能够完成任务,再考虑:

  • 能不能减少遍历次数;
  • 能不能降低时间复杂度;
  • 能不能压缩辅助空间。

这几年,“暴力解 408 算法题”的内容很受欢迎,背后确实有现实原因:

一个正确、完整、能说明复杂度的朴素算法,通常比一段思路高级但漏洞很多的代码更有价值。

但要注意:

暴力解是一条保底路线,不是万能口诀。

暴力解本质上通常是枚举。

如果题目:

  • 明确限制空间;
  • 要求较高的时间效率;
  • 或者朴素做法明显无法满足条件;

那你仍然需要掌握:

  • 快慢指针;
  • 双指针;
  • 哈希;
  • 递归;
  • 分治;
  • 辅助数组;
  • 排序后处理。

所以更准确的策略是:

平时训练时,朴素解和最优解都要会;考场上实在想不到最优解,也要先保证有东西可写。


第三步:把代码放进编译器里验证

408 最终是手写,但前期不要拒绝编译器(请注意,是在前期时间充足的情况下)。

很多初学者手写代码时,自认为逻辑没有问题,实际一运行,错误一大堆:

  • 指针没有初始化;
  • 循环条件少写了等号;
  • 删除结点后还在访问原地址;
  • 数组越界;
  • 递归缺少出口;
  • 变量更新顺序写反;
  • 链表断掉后找不到后续结点。

把代码放进编译器运行,可以迅速暴露这些问题。

你以为写对了,和它真的能处理各种情况,不是一回事。

前期可以在电脑上写,适当设计几个测试样例,例如:

  • 空表;
  • 只有一个结点;
  • 所有元素都相同;
  • 删除的是第一个元素;
  • 删除的是最后一个元素;
  • 目标元素连续出现。

等基本语法稳定后,再逐渐转到纸上。


第四步:一定要回到纸上默写

只在电脑上写也不行。

IDE 会帮你:

  • 自动补全括号;
  • 提示变量名;
  • 标红语法错误;
  • 自动缩进;
  • 甚至直接给出代码片段。

但考试时,这些都没有。

所以到了强化阶段,一定要专门练纸笔。

比较实用的方式是:

每学完一类题,选两三道典型题,隔一天在白纸上重新写一遍。

写完后对照答案,不只看结果对不对,还要检查:

  • 变量有没有定义;
  • 指针含义是否清楚;
  • 循环边界是否正确(其实考试不严格要求);
  • 是否处理空表;
  • 是否处理单结点;
  • 是否处理长度为 1 的情况;
  • 时间复杂度有没有写;
  • 空间复杂度有没有写。

基本算法最好做到:

能够手写、能够手推、能够自己模拟运行过程。

较难的算法不一定要逐行背下来,但必须理解实现过程。

这个标准,比“把整本代码全部背下来”靠谱得多。


四、到底要不要背模板?

答案是:

要背,但不能只背。

数据结构代码题确实存在一批高频骨架:

  • 数组遍历;
  • 链表的插入、删除、逆置、合并;
  • 快慢指针找中点;
  • 双指针从两端处理;
  • 栈和队列的基本操作;
  • 二叉树递归遍历;
  • 二叉树层序遍历;
  • 图的 DFS 和 BFS;
  • 常见排序过程;
  • 二分查找。

这些内容如果每次都从零推导,考试时间肯定不够。

所以,必须熟练到能够快速写出来。

但“背模板”不等于逐字背答案。

真正值得背的是:

  • 函数参数怎么设计;
  • 指针或下标分别表示什么;
  • 循环不变量是什么;
  • 什么时候移动指针;
  • 什么时候修改链接关系;
  • 递归函数的结束条件是什么。

比如链表逆置,与其死背十行代码,不如记住三个角色:

  • pre:保存已经逆置好的部分;
  • p:指向当前需要处理的结点;
  • r:提前保存后继,防止断链后找不到剩余链表。

只要这三个指针的意义懂了,即使变量名换掉、题目包装换掉,你照样能写。

真正要记住的不是代码长什么样,而是每个变量承担什么任务。

如果只记代码外形,不理解变量作用,一紧张就容易出现这种问题:

“这一行到底应该放在指针后移之前,还是后移之后?”


五、要不要刷力扣?答案不是简单的“要”或“不要”

这是经验贴里争议最大的一点。

一部分上岸考生认为:

408 代码题只需要掌握辅导书里的经典算法,再认真做历年真题,不必专门刷大量 LeetCode。

这个观点有道理。

因为力扣更侧重:

  • 算法应用;
  • 在线判题;
  • 完整代码实现;
  • 边界测试;
  • 某些技巧型解法。

而 408 除了代码大题,还有大量:

  • 概念题;
  • 性质题;
  • 计算题;
  • 综合应用题。

力扣不能覆盖数据结构专业课的全部要求。

而且,其中不少动态规划、回溯、复杂技巧,并不属于 408 的复习重点。

如果投入过多时间,大量刷题很容易挤占其他科目的复习时间,

但另一部分人的意见也不是错的。

对于代码基础很弱、经常出现“思路懂,但就是写不出来”的同学,适量做简单题确实有帮助。

在线判题可以帮你:

  • 发现边界问题;
  • 纠正语法错误;
  • 训练函数实现;
  • 把自然语言思路转化成代码。

所以,更合理的结论是:


下面几类人,不需要专门刷大量力扣

  • 本科期间写过不少代码;
  • 基本数组、链表和树题能够独立实现;
  • 历年真题大部分能写出思路;
  • 复习时间紧;
  • 数学或其他科目还有明显短板。

这类同学以以下内容为主,基本就够了:

  • 王道或天勤或竟成或其他教辅里的经典代码;
  • 历年真题;
  • 自己整理的专题题目;
  • 纸笔手写训练。

下面几类人,可以适量刷简单题

  • 跨考或几乎零代码基础;
  • 知道算法思路,却经常写不出循环和递归;
  • 链表、树的指针操作总是出错;
  • 还需要兼顾复试机试。

刷题范围不要无限扩大,围绕 408 考纲即可:

  • 数组;
  • 字符串;
  • 链表;
  • 栈;
  • 队列;
  • 二叉树;
  • 图的遍历;
  • 查找;
  • 基础排序。

难度建议:

以简单题为主,少量中等题即可。

不要为了一个和初试关系不大的技巧题,耗掉一整个下午。


六、真题应该怎么用,才不算浪费

很多人把代码真题留到最后,觉得做早了,以后就没有题目可以模拟。

其实,代码题没有必要这样留。

历年真题数量有限,但它的价值不只是测试分数。

更重要的是:

帮助你认识 408 到底喜欢怎么考。

有些题考:

  • 数组上的空间换时间;
  • 链表拆分、逆置和重新连接;
  • 树的遍历与判断、统计、查找;
  • 复杂度限制下的算法设计;
  • 多个基本操作的组合。

真题至少可以分三轮使用。


第一轮:只做题型识别

看完题后,先判断:

  • 考的是什么数据结构;
  • 可能需要哪些经典操作;
  • 朴素解是什么;
  • 优化方向是什么。

写不完整没有关系。

这一轮的重点是:

建立题型和方法之间的联系。


第二轮:完整手写

这一轮要完整写出:

  1. 算法思想;
  2. 核心代码;
  3. 时间复杂度;
  4. 空间复杂度。

做完不要只对最终代码,还要关注:

  • 哪些步骤是核心评分点;
  • 哪些错误只会扣部分分;
  • 哪些地方会导致整体逻辑失效;
  • 朴素解和最优解的差距在哪里。

要逐渐建立一个认识:

一道代码题,并不是只有“全对”和“零分”两种结果。


第三轮:限时和复盘

考前可以把真题按类型打乱,不再按照年份或章节做。

每道题先限制思考时间。

到时间还想不到最优解,就练习:

  • 写出合理的朴素方案;
  • 把已经想到的步骤规范表达出来;
  • 写清楚时间复杂度和空间复杂度;
  • 尽量保住能够拿到的过程分。

复盘时,不要只记一句“这道题错了”。

要进一步标记错因:

  • 没看懂题意;
  • 数据结构选错;
  • 算法思想没想到;
  • 思路对,但写不出来;
  • 边界遗漏;
  • 指针更新顺序错误;
  • 复杂度判断错误。

不同错因,解决办法完全不同。

复盘不是记住答案,而是找到自己为什么写不出来。


七、最值得整理的不是“万能模板”,而是自己的错题代码本

代码题笔记不要抄得太厚。

真正有用的内容,可以分成四类。


第一类:基础模板

例如:

  • 链表操作;
  • 树的遍历;
  • 图的遍历;
  • 排序;
  • 查找;
  • 栈和队列的基本操作。

第二类:常用算法思想

例如:

  • 双指针;
  • 快慢指针;
  • 辅助数组;
  • 哈希标记;
  • 递归;
  • 分治;
  • 先排序再处理;
  • 空间换时间。

第三类:自己经常犯的错误

例如:

  • 链表断链;
  • 数组越界;
  • 递归没有出口;
  • 删除结点后继续访问原地址;
  • 指针移动过早;
  • 循环边界少写一个等号。

第四类:真题中的组合方式

例如:

找中点 + 逆置 + 交叉合并

不要只把它记成一道孤立的题。

要拆成几个能够重复使用的基本操作。

比如一道链表重排题,可以拆成:

  1. 快慢指针找中点;
  2. 后半段原地逆置;
  3. 两段链表交替合并。

这种拆法,比死背完整答案更有迁移价值。

以后再遇到:

  • 链表重排;
  • 回文判断;
  • 折半处理;
  • 两段链表交叉操作;

就有可能复用其中的一两步。

不要只记“这道题怎么做”,要记“这道题由哪些基本操作拼出来”。


八、一个相对稳妥的训练安排

如果现在还在基础阶段,可以按照下面的节奏来。


基础阶段

重点是:

看懂数据结构和基本代码。

每章学习完,写 3~5 个最核心的基础操作。

不追求难题,也不追求第一次就写得完全正确。


强化前期

开始按专题练习:

  • 数组和顺序表;
  • 链表;
  • 栈和队列;
  • 树;
  • 图;
  • 查找;
  • 排序。

每道题按照这个顺序练:

先说思路 → 再写代码 → 最后放进编译器验证。


强化后期

集中做历年真题。

同一道题最好经历下面几个步骤:

独立思考 → 参考答案 → 重新手写 → 隔几天再写

真正的掌握,不是当天看懂。

而是隔几天以后,没有答案,你还能写出来。


冲刺阶段

减少新题,增加:

  • 纸笔默写;
  • 限时训练;
  • 错题复盘;
  • 高频模板回顾;
  • 真题组合方式整理。

每天不用写很多,保持手感即可。

重点复习自己反复出错的地方,而不是继续盲目扩充题量。


九、最后说几个很容易踩的坑

1. 不要无限收集资料

代码题资料永远找不完。

王道课后题、真题、专题题单和大量网课一起开,结果通常不是学得更全面,而是每套资料都只做了一点。

选一套主线资料,再用真题检验,已经足够。


2. 不要把抄代码当成练代码

抄得再工整,也不能证明你会写。

至少要有一次:

合上答案,从空白开始。


3. 不要每道题都追求最优解

理解最优解很重要,但考试首先要有得分能力。

先写对,再优化。


4. 不要只练电脑,不练纸笔

考试没有自动补全,也不会提示你少写了一个括号。

电脑负责发现错误,纸笔负责适应考试。

两种训练都需要。


5. 不要因为一道题卡住,就否定自己的代码能力

408 算法题本来就不是看两遍答案就能掌握的内容。

真正的学习过程往往是:

完全不会 → 能看懂 → 能复述 → 能模仿 → 能独立写

中间反复忘、反复错,都很正常。


写在最后

408 数据结构代码题真正需要的,不是刷几百道算法题,也不是背下一本代码答案。

更重要的是:

  • 把有限的经典操作练熟;
  • 把真题中常见的组合方式看懂;
  • 能在没有编译器的情况下,把自己的思路写成阅卷老师看得明白的代码。

基础差,就先补 C 语言和基本操作。

思路有但写不出,就多做“口述思路到代码”的转换。

代码能写但容易错,就用编译器检查,再回到纸上默写。

冲刺阶段想不到最优解,也不要空着,先把正确的朴素方案和复杂度写出来。

代码题不需要神化,也不能完全放弃。

稳稳地把基础操作、真题和手写能力练出来,这道题就不会像刚开始看上去那么吓人。

我整了一份《408 数据结构核心代码模板整理》并做成了 PDF,方便大家保存、打印,或者在暑假复习时随时查阅。

(原创且免费公开)公众号后台回复:代码题

领取《408 数据结构核心代码模板整理》PDF

相关学习资料