拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++刷题笔记体系:以数据结构为主索引的高效刷题法

C++刷题笔记体系:以数据结构为主索引的高效刷题法 刷题这件事门槛真不在会不会写代码而在刷完之后能留下什么。我见过不少同学力扣刷了三四百题一问到某类题的核心套路还是得现场翻题解也见过刷题只有一百出头的朋友面试手撕算法时却像开了挂一样稳。差别在哪儿就在笔记。不是他记得多而是他有一套能把题目、数据结构和代码模板串起来的笔记结构。这篇文章不是题解合集而是一套我打磨了很久的C刷题笔记方法论。前后改了三版踩了无数坑最后沉淀下来一套以数据结构为主索引、以题目和代码模板为内容的结构化笔记体系。它解决的痛点非常明确如何让你刷过的每道题、学过的每个数据结构都能在30秒内被定位和调用。不管你是准备C方向的秋招面试还是在复习408统考里的数据结构部分或者刚入算法竞赛想建立笔记体系这套结构都能直接抄作业。1. 刷题笔记的整体设计思路1.1 为什么你的笔记需要结构而不是收藏先说一个扎心的事实绝大多数人的刷题笔记本质上是个题解收藏夹。我自己第一版笔记就是这样的——按题号建文件夹每道题一个Markdown文件里面抄一遍最优题解标注一下这题用双指针然后就再也不看了。刷到三百题的时候这个笔记变成了一个数字字典想查链表环的入口怎么写我得挨个翻文件名翻到一半就放弃了。后来我才意识到一个关键的逻辑问题按题号组织笔记维度搞错了。题目是无限的数据结构是有限的。你刷一万道题底层的数据结构也就那么十几种。笔记的主索引应该是数据结构题目只是挂在数据结构下面的具体案例。这样才能做到遇到新题 → 定位到数据结构 → 找到相似题的套路 → 复用模板。我当时给自己定了一个目标任何一道刷过的题我在30秒内能在笔记里找到它对应的数据结构标签、核心解法和代码模板。这个目标直接决定了笔记的组织方式也让我下决心把之前的数字典全部推翻重来。1.2 四层笔记骨架知识索引、专题、题目、错题本最终定型的笔记骨架分四层每一层解决一个不同的问题第一层知识索引也就是按数据结构分类的总目录相当于整个笔记的导航栏。第二层专题笔记记录每个数据结构的核心考点、经典套路和适用场景相当于知识地图。第三层题目笔记具体某道题的思路拆解、代码、复杂度和边界条件相当于案例库。第四层错题与反思记录踩过的坑、反复错的题和修正后的理解相当于检修记录。这四层的关系不是平行的而是层层索引。知识索引在最顶层往下一层是专题笔记再到具体的题目笔记。错题本则是一个横向的切片从所有层里把值得反复看的东西抽出来。这个结构里最容易忽略的是第二层专题笔记。很多人记笔记直接就进入第三层写题目结果就是每道题都是孤立的看不出题目之间的联系。专题笔记的核心价值在于提炼套路比如单调栈解决下一更大元素问题前缀和解决子数组区间和问题这些才是刷题真正沉淀下来的东西。2. 数据结构知识框架的梳理方法2.1 线性结构笔记数组、链表、栈、队列怎么组织线性结构是刷题里出现频率最高的一类也是笔记最好整理的一类。我的做法是给每种结构单独建一个专题页页里面固定几个小节核心性质、常用操作、经典套路、对应题目列表。以数组为例核心性质就一句话连续内存、随机访问O(1)。但它的经典套路特别多前缀和、差分数组、双指针、滑动窗口、原地哈希。这些套路之间是有联系的比如滑动窗口本质上是双指针的一种变体我在笔记里会用箭头把这些联系起来而不是平铺直叙地列一堆标签。链表这一块笔记里必须单独突出三个模板虚拟头节点、快慢指针、反转链表。虚拟头节点解决的是头节点可能被删除的边界问题快慢指针解决环检测和找中点反转链表是很多复杂题的基础操作。这三个模板我会要求自己能默写出来笔记里也专门放了一份带注释的参考代码。栈和队列的笔记重点在结构变体单调栈、单调队列、双端队列。我见过很多人在哈希表和普通栈上花大量时间但面试和竞赛里真正拉分的是单调栈——下一个更大元素那一类题单调栈模板一上代码量从O(n²)暴力降到O(n)线性。这类专题笔记值得你做一份完整的代码模板并附上适用场景的判断方法。2.2 树和图从遍历到进阶套路的记录树的一块内容是所有数据结构笔记里最不能省模板的。二叉树的递归遍历、迭代遍历、层序遍历必须每个都有一份可运行的参考代码。我在专题笔记里会把递归遍历单独放在最前面因为它最直观然后用迭代遍历和递归做对照说明显式栈是怎么模拟调用栈的。BST二叉搜索树的笔记重点记一条中序遍历有序这一条性质能解决一大半BST题目。堆和优先队列我的笔记里会和Top K问题求中位数放在一个专题下。这两个场景几乎是绑定的因为堆的核心优势就是维护一个动态集合的最值。而并查集我会单独给一个专题页因为它太常被忽略了——很多连通性问题用并查集可以写得比图论算法简洁得多。并查集的笔记内容固定为初始化、查找带路径压缩、合并按秩合并、以及常见变体。图的部分不要一上来就搞邻接矩阵还是邻接表的选择困难。刷题场景里邻接表是绝对的主流笔记里直接记邻接表的建图模板就行。图的遍历DFS/BFS和树是相通的但要注意visited数组的存在——树的遍历不需要这个图的遍历必须要有这是个极易踩坑的点。再往上就是拓扑排序、最短路这些算法专题我的做法是每个算法单独一节附一道经典题和一个标准模板。2.3 哈希表、排序与工具型结构的速查哈希表这类结构没有太多复杂的套路但它是个万金油工具。我的笔记里哈希表的专题页重点记录的是什么时候想到用它O(1)查找需求、去重需求、两数之和这类配对问题、LRU Cache的手写实现场景。另外要特别记录的是哈希表的工程细节比如unordered_map的底层是哈希表map的底层是红黑树这直接关系到能不能保持有序。排序算法这一块如果目标是面试笔试建议做一张对比表把这些信息整理清楚冒泡排序、选择排序、插入排序在O(n²)档归并排序在O(n log n)档且稳定快速排序平均O(n log n)但最坏O(n²)堆排序O(n log n)但不稳定。我会在笔记里额外标注每个排序的适用场景——比如需要稳定排序时优先归并大部分情况下快排最快。STL容器的速查表也属于这一类工具型笔记。vector、deque、list、stack、queue、priority_queue、set、unordered_set、map、unordered_map每个容器一行记清楚底层结构、插入删除查找的复杂度、是否有序、有没有重复。这张表不是背的是查的——刷题时不确定就用两秒看一眼三个月后自然就熟了。3. 从刷题到沉淀题解笔记的实操拆解3.1 一道典型题目的笔记应该包含什么题目笔记是三层结构里最具体的一层也是新手最容易写歪的一层。很多人写题解笔记就是贴代码再抄一段思路这不是笔记这是存档。我自己的题目笔记固定包含五个部分题目概述、数据结构标签、核心思路、参考代码、复杂度与边界。这里我用一道经典题——LRU缓存的实现举例。第一步题目概述必须用自己的话写不能抄原题。比如LRU Cache我会写设计一个容量固定的缓存get和put操作平均复杂度O(1)满时淘汰最久未使用的key。这就把题目翻译成了人话也直接点出了核心难点怎么做到O(1)淘汰最久未使用。第二步数据结构标签。LRU这题的标签是哈希表 双向链表。我会在旁边补一句为什么是这两个结构组合哈希表负责O(1)查找双向链表负责O(1)删除和移动节点。这个组合是面试常考的数据结构组合拳值得单独在标签旁边打一颗星。第三步核心思路按操作拆解get时如果key存在把对应节点移到链表头部并返回值put时如果容量满了删除链表尾节点和对应哈希项再插入新节点到头部。这一步不要写大段文字用简单的步骤列表每步对应一个操作。第四步参考代码必须是自己重新默写过的版本不能直接复制题解。默写一遍你就会发现最容易错的是哈希表里存的是迭代器/指针这个细节以及双向链表的边界操作。第五步复杂度与边界。时间复杂度是O(1)空间复杂度是O(capacity)。边界条件至少写三条容量为0时put的行为、get一个不存在的key、put一个已经存在的key时容量已满。这三条每一条都对应一个潜在bug有了这个记录二刷时能节省大量debug时间。3.2 C代码模板沉淀二分、排序与STL速查代码模板是整个笔记体系里含金量最高的一部分。我强烈建议单独建一个templates目录里面放所有高频模板的可运行代码。这里分享几个C刷题高频模板都是我反复默写后定稿的版本。整数二分是最容易出错的模板因为边界条件极其反直觉。我用的是两个版本找左边界和找右边界。// 找左边界满足条件的最小索引 while (left right) { int mid left (right - left) / 2; if (check(mid)) right mid; else left mid 1; } // 找右边界满足条件的最大索引 while (left right) { int mid left (right - left 1) / 2; if (check(mid)) left mid; else right mid - 1; }关键点就两个第二个模板的mid要加1防止死循环check(mid)为真时收缩的方向决定了找的是左还是右边界。这两个模板我建议抄进笔记后用lower_bound和upper_bound的行为去验证一遍。排序模板里快排代码简洁但难在有大量小细节我笔记里保留的是带随机化选取基准的版本。归并排序则永远要配一份因为它除了排序还能解决逆序对问题。另外STL的sort、stable_sort、partial_sort这几个函数的使用场景也得记清楚——partial_sort就是堆排序的STL实现找Top K时比全排序快。STL速查表里我用得最多的几个操作值得单独标出来lower_bound/upper_bound在有序容器上做二分next_permutation生成下一个排列accumulate求和string的substr和find。这些操作能省掉大量手写代码笔试环境里时间就是分数。3.3 复杂度分析与边界条件的记录方法题目笔记的第五部分——复杂度与边界是我认为和思路同等重要的内容但很多人根本懒得写。我的经验是复杂度分析不是给阅卷人看的是给你自己建立复杂度直觉用的。每次AC之后强迫自己写一句时间O(n)空间O(1)半年之后看到题目就能大概量级这在面试里是极大的加分项。边界条件记录我总结了一个清单每条对应一类经典bug边界场景典型错误应对方法空输入直接访问首元素崩溃开头判空或使用虚拟头节点单个元素循环条件写错导致漏处理先跑单元素用例再提交最大/最小值整数溢出用long long或mid left (right - left) / 2重复元素二分边界丢失用带等号的判断或直接套模板容器大小变化遍历时删除元素导致失效反向遍历或用迭代器返回值这张表我打印了一份贴在显示器边上后来直接融进了每个题目的笔记模板里。每次写题解笔记最后一步就是对照这个清单检查一遍检查通过才算是真正刷完了这道题。4. 刷题笔记常见问题与踩坑实录4.1 笔记记了但不想复习怎么办这个问题几乎每个人都会遇到包括我自己。第一版笔记失败的一大原因就是记完就扔从不回看。后来我找到一个很简单的机制解决了它——把错题本当成复习清单而不是偶尔翻翻的存档。具体做法是每周末从本周刷的题里挑出值得二刷的题加入错题本的待办清单二刷时如果5分钟内没有思路这道题就会一直留在清单里直到能独立写出来为止。这样一来笔记不再是被动记录而是一份被持续维护的待办列表。另外我还用了一个三遍法则第一遍看题解后自己默写代码第二遍隔三天独立重写一遍第三遍隔两周从头做一遍。笔记里每个题目都标注当前处于第几遍状态。这个标记机制看着土但效果出奇地好——它把复习从一个模糊的念头变成了一个具体的动作。4.2 边界条件与空指针错题本的正确打开方式错题本要记录的不只是这题我错了而是错的内部机制。我翻自己早期的错题本发现里面通篇写着空指针错误越界超时完全没有任何区分度。后来我把错题分成了四类每一类对应不同的处理方式第一类是边界边界条件缺失比如链表操作没考虑空节点。这类问题的解法是建立检查清单。第二类是思路方向错误——不是不会写而是选错了数据结构这类错题价值最高需要回到专题笔记去重看对应数据结构的适用场景。第三类是代码细节疏漏比如漏写return、递归压栈顺序反了这类要靠调试习惯没有捷径。第四类是复杂度估计错误交上去才发现超时这类要专门训练复杂度分析。错题本记录格式我固定为题目链接、错误现象、根因类别、当时的解题思路、正确思路、预防措施。预防措施是最关键的一行——比如链表题一律先画图双指针题先确认移动条件这些是能从一次错误中提炼出通用规律的部分。4.3 C语言细节的坑随机数、字符串与容器操作刷题笔记里专门有一节是C语言坑位记录记录那些不是在算法层面而是在语言层面的坑。第一个高频坑是随机数。刷题时用rand()配合%取模在数据量小的时候没问题但rand()的分布并不均匀在高频场景下会影响正确性更推荐用random库的mt19937。std::mt19937 rng{std::random_device{}()}; int val std::uniform_int_distributionint(0, n - 1)(rng);第二个高频坑是字符串处理。字符转数字时0转int要用s[i] - 0忘了减就直接取到ASCII值逻辑全错。分割字符串时C的istringstream可以按空格分割但按特定分隔符分割就需要自己写这个工具函数建议提前准备好放在模板库。第三个坑是容器遍历时删除元素。在vector里边遍历边删除会让迭代器失效通常的做法是从后往前遍历或者搭配erase-remove惯用法。在unordered_map里删除元素也一样要注意迭代器失效问题——把删除操作放在循环体末尾或者先记录再统一删除。这些坑每个我都单独记录过后来刷题时遇到类似问题能直接翻笔记秒懂。4.4 工具链问题vscode配置C/C环境刷题笔记里加一节工具链配置看起来不相关但实际上是效率杀手。我见过太多人在环境上浪费整晚导致刷题计划断档。我的主力工具是VS Code配MinGW整理出了一套稳定配置分享出来给大家少走弯路。首先是编译器安装Windows下装MinGW-w64装完后把bin目录加到系统PATH环境变量。VS Code里安装三个扩展C/C、C/C Extension Pack、Code Runner。然后配置编译调试核心是两个文件tasks.json负责编译launch.json负责调试。我的tasks.json里编译命令一般是{ type: cppbuild, command: g, args: [-fdiagnostics-coloralways, -g, ${file}, -o, ${fileDirname}/${fileBasenameNoExtension}.exe], options: {cwd: ${fileDirname}}, problemMatcher: [$gcc], group: {kind: build, isDefault: true} }最坑的往往是两个问题一是中文乱码Windows下终端默认编码是GBK需要在设置里把terminal.integrated.profiles.windows的编码改成UTF-8或者在源码文件里保持统一编码二是调试器找不到这时要检查launch.json里的miDebuggerPath是否指向了MinGW的gdb.exe路径。这套配置我完整整理在笔记的工具链章节里每次重装系统照着做一遍就行20分钟能搞定再也不用花一个晚上折腾。5. 笔记工具与复习节奏的工程化建议5.1 工具选型Markdown Git还是在线文档笔记工具这个事我的建议是别纠结用Markdown Git。市面上各种笔记软件我基本都用过一圈最后回到了纯本地Markdown原因有三个。第一Markdown是纯文本不绑定任何平台将来想迁移到任何工具都零成本。第二配合Git可以做版本管理笔记内容可以回溯——这看起来小题大做但对一份持续维护两三年的笔记来说价值很大。第三刷题笔记里大量包含代码块Markdown在代码展示上天然清晰VS Code直接编辑和预览很方便。如果你的笔记里有很多手绘图解数据结构那可以搭配一个支持附件的工具或者直接用draw.io画好图放在assets目录。但笔记主文件还是Markdown这是最稳妥的。目录组织我分享一个可以直接用的树形结构cpp-notes/ ├── README.md # 知识索引总目录 ├── 01-linear/ # 线性结构专题 │ ├── array.md │ ├── linked-list.md │ ├── stack.md │ └── queue.md ├── 02-tree-graph/ # 树和图专题 ├── 03-algorithm/ # 算法专题二分、排序、DP等 ├── 04-problems/ # 题目笔记按专题分子目录 ├── 05-errors/ # 错题本与语言坑位 └── templates/ # 代码模板库这个目录结构本身就是一个知识索引打开README.md就能看到所有数据结构专题的链接和各自的经典题目列表。5.2 复习节奏新题、二刷、三刷怎么安排笔记工具选好了还得配一套可持续的刷题节奏不然笔记又会变成存档。我的个人实践是固定一个周期节奏每天2道新题加0到1道二刷每周做一次专题总结每两周做一次错题本的全面清理。新题的选择不要随机乱刷。我的做法是跟着专题走比如本周聚焦单调栈专题就只刷单调栈相关的题。刷完当天立刻写题目笔记这步不能拖一拖就没动力写了第二天再看的概率很低。二刷放在三天后独立重写代码不看题解、不看笔记然后对照笔记找出遗忘的知识点标记。三刷放到两周后复习专题时顺手做一遍错题本上的题能独立AC的就从待办清单移除不能AC的再往后推一轮。我个人体会最深的一点是复习节奏的本质不是努力而是对抗遗忘。笔记本身是外置大脑而错题本和刷题节奏是这个外置大脑的刷新机制。只要这个刷新机制还在跑你的数据结构知识框架就会像滚雪球一样越滚越扎实。最后分享一个我自己一直在用的小技巧每周花20分钟只看专题笔记而不看任何题目试着凭记忆画出每个数据结构的知识脉络。画不出来的地方就是下周需要重点补的部分。这个动作让我的笔记结构始终保持活跃而不是越堆越厚、从不动用。刷题笔记的意义就在于此——它不该是一份越积越重的存档而该是一个随时能被你调用的、越用越顺手的工具箱。
返回列表