资讯动态

数据结构与算法笔记高效整理指南:从模板到实战

发布时间:2026/10/17 5:13:53 来源:尧图企业网站定制
很多刚开始接触数据结构和算法的人都容易陷入一个误区把书从头翻到尾把代码抄了一遍又一遍感觉什么都看懂了可一到写代码或者面试的时候脑子里还是一团浆糊。我之前也是这个状态直到后来换了一种方式整理笔记才真正把这块硬骨头啃下来。这篇内容不是什么教科书式的长篇大论而是我把自己整理数据和算法笔记的完整思路、常用套路、以及踩过的坑一次性分享出来。如果你也处在看了就忘、懂了不会用的阶段这篇内容应该能帮你省下不少摸索的时间。1. 为什么你的笔记总是记了等于白记很多人记数据结构笔记就是照着教材画几个结构图抄几段标准代码然后往收藏夹里一扔再也没打开过。这种笔记的问题不在于记这个动作本身而在于它缺少了两个关键环节论证和对比。我一开始也是这么干的。比如线性表我抄了顺序表和链表的所有操作代码甚至把每个函数的时间复杂度都标注得清清楚楚。但真到用的时候让我设计一个 LRU 缓存我完全不知道该用哪种结构就算知道要用哈希表加双向链表也写不出来。问题出在哪笔记里只有答案是A没有为什么不是B没有把两种结构放在同一道场景题里去对比没有记录决策的整个过程。后来我调整了记笔记的方式每个知识点必须回答三个问题才允许写进笔记。这三个问题是这个结构解决了什么问题它的不可替代性在哪里它和相近结构的边界在哪什么情况下必须切换如果我来设计这个结构我会踩哪些坑这三个问题逼着我把每个知识点都过了一遍脑子而不是单纯复制粘贴。尤其是第一个问题它决定了笔记的深度。比如数组表面上是连续内存、随机访问但它的不可替代性在于CPU 缓存友好和O(1) 下标定位。这两个特性决定了数组在很多高性能场景下依然是王者比如内存池、帧缓冲、消息队列的底层存储。而且我还有一个习惯用伪代码讲逻辑用真实代码验证细节。笔记里先用中文把逻辑链路写清楚再附上可运行的代码和运行结果。这样复习的时候第一遍看逻辑第二遍看代码效率会高很多。很多人的笔记之所以废掉就是因为直接抄了一堆别人写好的代码既没有自己推演过也没有运行过自然记不住。2. 我整理笔记时总结的高频结构速查表在动笔写任何代码之前先把下面这张速查表刻在脑子里。这张表是我从几百道实战题和实际项目中提炼出来的基本涵盖了日常开发里 90% 以上的结构选择问题。结构核心特性典型应用场景最容易踩的坑数组O(1) 随机访问缓存友好矩阵运算、缓冲池、哈希表底层插入删除导致大量数据搬迁链表O(1) 插入删除已知节点LRU、任务队列、文件系统块链边界节点 null 处理、内存碎片栈后进先出函数调用、括号匹配、表达式求值栈溢出递归过深队列先进先出BFS、消息队列、缓冲区循环队列边界判定哈希表平均 O(1) 查找缓存、去重、索引哈希冲突、扩容抖动树层级关系、O(log n) 平衡查找数据库索引、文件目录、表达式树退化链表、平衡因子更新堆快速获取最值优先队列、TopK、定时器堆化方向、索引计算错误图表达多对多关系路径规划、社交网络、依赖分析环检测、DFS 递归爆栈并查集快速判断连通性岛屿问题、朋友圈、动态连通性路径压缩与按秩合并顺序这张表的价值不在于背下来而在于每次做题或做设计时对照它问自己我这个场景最核心的操作是什么比如消息队列的核心操作是先进先出、尾部进头部出那基本就是队列的天下如果还要支持按优先级出队那就得上堆。再比如一个系统需要快速判断用户是否在某个黑名单里核心操作是 O(1) 查找哈希表就是不二之选。这里还要多说一句结构的选择从来不孤立于业务场景。同样的数据量如果是一万个元素和一百万万个元素选择可能完全不同。一万个元素的线性查找可能比哈希还要快因为哈希有计算哈希值和解决冲突的开销而线性查找就是一次干净的循环。笔记里记录这种场景-数据量-结构的决策链路比记录一百个定义都更有价值。3. 算法笔记的正确打开方式从递推到分治的实战链路很多人的算法笔记是一个一个孤立的算法堆在一起比如冒泡排序是这样写的快速排序是这样写的归并排序是这样写的。这种笔记的致命伤是看不到算法之间的演进关系。实际上大部分经典算法都不是凭空冒出来的它们是从最简单的暴力思路一步步优化出来的。我在笔记中特别喜欢记录这种演进链。以排序为例完整的演进链条是这样的暴力每次找最小放前面 - 选择排序优化相邻交换像冒泡一样把大数顶上去 - 冒泡排序再优化插入一张牌到已排序区 - 插入排序进阶分而治之每次分成两半分别排好再合并 - 归并排序更进阶选一个基准把数据分成大小两边递归 - 快速排序数学飞跃利用元素本身的范围不比较直接放 - 计数排序、桶排序、基数排序我非常推荐每位学习者把这类演进链亲手画成一张图从最简单的模型开始每一步的优化动机是什么时间复杂度怎么变化空间换时间体现在哪里。你不需要背任何算法的代码只要能把整条链路推演出来代码自然而然就写出来了。3.1 递归问题先画树再写代码递归是很多人刚开始学算法时的一道坎。我记笔记时总结了一条铁律凡是递归问题先在草稿纸上画出递归树再动笔写代码。递归树能帮你直观看到每一层做了什么递归深度是多少有多少个重复子问题有没有优化的空间比如记忆化拿斐波那契数列举例。直接递归的写法大家都会但它的递归树里充满了重复计算时间复杂度是恐怖的 O(2^n)。画完递归树之后你会发现很多子树被反复计算。这时加上一个 memo 数组做记忆化复杂度立刻降到 O(n)。如果你能再从递推的角度把它改成迭代甚至能优化到 O(1) 空间。这些递进的过程我在笔记里都是用先图后码的方式记录的先画树再标注重复计算的节点再写上优化代码。这才是真正有用的算法笔记因为它在记录你的思考过程而不是结果。3.2 分治与回溯理解递和归两个阶段分治和回溯是递归的两种重要形式。分治的典型特征是把大问题拆成互不相干的小问题分别解决再合并回溯则是尝试所有可能的路径走不通就回头。我在笔记里特别强调要区分这两者因为很多初学者混为一谈。分治的代表是归并排序和快速排序。归并排序的递阶段不断二分直到单个元素天然有序归阶段用双指针合并两个有序数组。快速排序的递阶段选基准并分区让左小右大归阶段其实什么都没做因为局部有序已经全局有序。回溯的代表是八皇后、全排列、组合和数独。它们的代码框架高度统一几乎所有回溯问题都可以套用这个模板def backtrack(路径, 选择列表): if 满足结束条件: 记录结果 return for 选择 in 选择列表: 做选择 backtrack(路径, 选择列表) 撤销选择我建议你在笔记里把做选择和撤销选择这两步用不同颜色的笔标注出来。撤销选择是最容易漏的一步漏了就会出现排列重复或者结果错乱。每次写完回溯代码先在心里模拟一次做选择-递归-撤销选择的完整循环再运行验证。4. 必须吃透的十个高频算法模板刷题和面试多年我发现高频算法其实高度集中在十个模板上。这些模板不是答案本身而是分析问题的框架。笔记里我会以模板变形的方式记录确保任何题目都能在这套框架里找到位置。4.1 二分查找模板二分查找的细节非常容易出错主要是在区间边界定义上。我自己用的一直是左闭右闭的写法模板如下def binary_search(nums, target): left, right 0, len(nums) - 1 while left right: mid (left right) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 else: right mid - 1 return -1核心要点就是循环不变量每次循环开始时target 一定在 [left, right] 这个闭区间内。只要你保证这一点边界就不乱。记笔记时我把三种常见变形查找第一个等于、最后一个等于、最接近 target 的数都列在旁边并注明各自的终止条件和返回值。做这类题最忌讳死记硬背四个模板一定要自己推导一遍区间收缩过程。4.2 滑动窗口模板滑动窗口用来解决子串/子数组的最值问题尤其是最长无重复子串最小覆盖子串。模板化的写法是left 0 for right in range(len(s)): # 右指针扩张更新窗口内数据 while 窗口不合法: # 左指针收缩更新窗口内数据 left 1 # 更新结果理解这个模板的关键点在于窗口扩张是无条件移动 right窗口收缩是有条件移动 left。什么时候收缩取决于题目定义的合法状态这需要你为每个具体问题设计一个计数器或哈希表来维护窗口状态。笔记里我会刻意把每个题的窗口不合法条件单独标注因为这是最容易因题而异的地方。4.3 双指针模板双指针在有序数组、链表问题中非常常见包括对撞指针和快慢指针。对撞指针的典型是两数之和。快慢指针的典型是链表中点、链表判环。快慢指针的进阶用法是找环入口这个推导过程非常优美很值得记在笔记里。设快指针每次走两步、慢指针每次走一步相遇后让慢指针回到头节点快指针改成每次走一步两者再次相遇的位置就是环入口。建议在笔记里推导一遍数学过程而不是直接背结论。4.4 BFS 与 DFS广度优先搜索BFS天然适合求最短路径或最小步数因为它是逐层扩散的。DFS 则天然适合枚举所有路径的场景。BFS 模板from collections import deque def bfs(start, target): queue deque([start]) visited set([start]) step 0 while queue: for _ in range(len(queue)): node queue.popleft() if node target: return step for neighbor in get_neighbors(node): if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) step 1visited 集合是必须的没有它图里的环会导致死循环。我见过太多人在做网格类 BFS 题目时忘了 visited导致超时。另外如果求的是最短路径的路径本身还需要配合一个 parent 数组记录每个节点的来源。4.5 动态规划五步法动态规划是很多人的噩梦但它的解题路径其实高度固定。我在笔记里总结了动态规划五步法定义 dp 数组的含义一维/二维下标代表什么找出状态转移方程从 dp[i-1] 或 dp[i-1][j-1] 等如何得到 dp[i]初始化 dpbase case确定遍历顺序从前到后、从后往前、两层循环哪个在外打印 dp 数组验证这五步缺一不可。尤其是第五步打印 dp 数组很多人觉得麻烦就跳过了结果代码运行时结果和预期完全不一致又不知道哪里出了问题。我在笔记里每个 DP 题都会附一张手工推导的 dp 表格比如背包问题的表格、最长公共子序列的表格这样一眼就能看出状态转移的正确性。背包问题、最长递增子序列、编辑距离、打家劫舍、股票买卖这几个经典题目建议每个都按五步法完整写一遍笔记之后遇到新题就往这个框架里套。4.6 回溯模板回溯的模板在前面已经展示过这里补充一个重要的剪枝思路在做选择之前先判断这个选择是否有必要继续。比如全排列问题中如果当前元素已经用过就直接跳过组合问题中如果剩余可选元素加上当前路径不够长度目标也可以剪枝。笔记里我会在每个回溯题旁边标注剪枝条件和剪枝前后的效率对比这能让你真正理解回溯 剪枝两个字为什么总是连在一起。4.7 堆的模板堆本身不是算法而是一种数据结构。但在解决 TopK、合并 K 个有序链表、数据流中位数等问题时堆的操作逻辑是反复出现的。Python 的话直接用 heapq 模块Java 用 PriorityQueue。核心技巧是大顶堆放最小的 K 个小顶堆放最大的 K 个。对于 TopK 问题如果一个一个全排序复杂度是 O(n log n)维护一个大小为 K 的堆复杂度是 O(n log K)在 K 远小于 n 时收益非常明显。数据流中位数则是一个大顶堆 一个小顶堆的经典配合大顶堆放较小的一半小顶堆放较大的一半中位数就在两个堆顶之间。4.8 并查集模板并查集常用于处理连通性问题比如岛屿数量、朋友圈、冗余连接。模板很短但有两个关键优化路径压缩和按秩合并。class UnionFind: def __init__(self, n): self.parent list(range(n)) self.rank [0] * n def find(self, x): if self.parent[x] ! x: self.parent[x] self.find(self.parent[x]) # 路径压缩 return self.parent[x] def union(self, x, y): rx, ry self.find(x), self.find(y) if rx ry: return False if self.rank[rx] self.rank[ry]: self.parent[rx] ry elif self.rank[rx] self.rank[ry]: self.parent[ry] rx else: self.parent[ry] rx self.rank[rx] 1 return True路径压缩让 find 操作近乎 O(1)按秩合并让树高度保持可控。如果没有这两步优化极端情况下并查集会退化成一条链复杂度退化到 O(n)切身体会很痛苦。4.9 前缀和与差分前缀和是用来快速求解区间和的经典技巧。比如给一个数组频繁询问 [l, r] 区间内元素的和朴素做法每次循环求和 O(n)前缀和数组可以把单次查询降到 O(1)。差分是前缀和的逆操作用于频繁给某个区间 [l, r] 统一加一个常数。这种技巧在处理大量范围更新时特别有效比如某区间内所有元素加 5。在原数组上操作是 O(n)用差分数组只需 O(1) 更新两个端点最后再做一次前缀和还原。这个技巧我建议记入笔记因为它在各种区间更新场景中是出镜率极高的隐藏套路。4.10 拓扑排序拓扑排序用来处理依赖关系问题。Kahn 算法基于 BFS是标准写法from collections import deque def topo_sort(n, edges): indegree [0] * n graph [[] for _ in range(n)] for u, v in edges: graph[u].append(v) indegree[v] 1 q deque([i for i in range(n) if indegree[i] 0]) res [] while q: node q.popleft() res.append(node) for neighbor in graph[node]: indegree[neighbor] - 1 if indegree[neighbor] 0: q.append(neighbor) if len(res) ! n: return [] # 有环 return res拓扑排序可以用来判断课程安排是否有环、编译系统的依赖顺序等。如果最终结果数量不等于节点总数说明图里有环这个检测点很关键我经常在笔记里用红色标注。5. 从会模板到会做题三遍刷题法笔记整理得再好不动手做应用题就是纸上谈兵。我用了很久才摸索出一套适合自己的刷题方法我把它叫做三遍刷题法想在这里详细说说。第一遍看题想思路。拿到一道题先不急着写代码。用前面那张速查表和十个模板对照题目这道题的核心操作是什么时间复杂度要求是多高应该用哪种结构是否可以转化成某个模板问题三分钟内想不出思路就直接看题解看完把题解的核心思路用自己的话记录下来。第二遍独立实现。遮住题解从头开始写代码。这一遍一定会卡壳卡在哪、为什么卡就是这次刷题最大的收获。写完之后一定亲手跑几个测试用例包括边界用例空数组、单元素、全是重复元素看看是否能正确通过。第三遍过几天再来一遍。过两三天之后重新做这道题尽量回忆思路然后独立写一遍。如果还能流畅写出来这道题才算真正掌握了。我通常用一个待办清单记录每道题的状态思路卡壳实现通过三天复现通过。这个方法用一句话概括就是永远不要在只看不写中浪费时间永远在独立复现中巩固。5.1 一道题的完整复盘示例这里以经典的最长连续序列题目为例完整走一遍我的笔记流程。题目大意给定一个无序数组找出数字连续的最长序列长度。要求 O(n) 时间复杂度。第一遍想思路最直观的做法是排序后扫描但排序是 O(n log n)不满足要求。那还有什么办法能在 O(n) 内判断一个元素的邻居是否存在哈希表把数组所有元素放进哈希集合然后遍历每个元素看看它的 num1、num2 是否存在但这样做最坏是 O(n^2)。优化点在于只有当 num-1 不存在时num 才是连续序列的起点只有起点才需要往后数。这样每个元素最多被访问两次整体复杂度 O(n)。实现之后在笔记里贴上最终代码和运行结果再记录几个关键边界用例比如空数组返回 0、全重复返回 1、负数同样适用。最后在三天后复现一遍确认思路和代码都还记得住。一套流程下来这个知识点的记忆深度远超抄十遍代码。6. 复习机制比笔记数量更重要整理笔记的真正目的是为了对抗遗忘所以复习机制必须提前设计好。我见过很多人的笔记越记越多但从来没回去翻过最终完全失效。笔记不是收藏而是索引你需要有一套触发复习的机制。我自己用的方法是三层复习法当天复习当天记完笔记睡前花十五分钟快速翻阅重点看逻辑推导过程。这一遍的目的是保证当天理解的逻辑没有被遗忘。间隔复习每隔三到七天把对应章节的笔记从头看一遍然后尝试脱离笔记复现其中的某个模板或某道经典题的思路。这一遍的目的是长期记忆。随机抽查在自己题库里随机抽几道题不翻笔记直接写。这一遍的目的是检验知识是否真正内化。为了便于间隔复习我建议笔记里加入错题/卡壳记录。每次做题卡壳我都用两句简短的话记录下来卡在哪里、为什么卡。这样下次复习时可以直接跳到自己最薄弱的环节而不是把已经会了的东西再看一遍浪费时间。这种做法比重复阅读效率高得多也算是我这些年整理笔记最受益的一个习惯。7. 学习资料推荐与避坑指南最后聊一下资料选择的问题这里踩的坑比较多值得花点篇幅说清楚。7.1 入门资料入门阶段最忌讳一上来就啃大部头理论书。我更推荐的路径是先刷一二十道简单题建立手感再回过头看理论、数据结构课程视频效率会高很多。数据结构视频和学习资料我最推荐的组合是在线课程平台上的算法入门课 一本经典算法书交叉阅读。视频负责直观演示动画书负责提供严谨证明和代码。建议不要只依赖一种视频能帮你建立直觉书能帮你补全细节。有一类图解算法书籍以大量配图讲数据结构非常适合零基础建立直观印象。但这类书通常深度有限看完后一定要及时转向更系统的教材否则很容易停留在看起来很懂的层次。7.2 刷题平台与题库刷题平台我主要用在线评测系统而不是本地 IDE。在线评测的好处是能实时反馈正确性和边界用例节省自己设计测试数据的时间。做题时建议按专题刷而不是随机乱刷。比如这个星期专攻链表下个星期专攻动态规划这样笔记也能按专题积累形成体系。题库方面业界公认的高频题清单可以按照数组 - 链表 - 栈队列 - 哈希 - 树 - 图 - 动态规划的顺序推进。每个专题刷 15~20 道就足以建立基本手感没必要死磕极端难题。7.3 成体系的参考书如果只选一本系统参考书我会推荐经典的算法导论类书籍它能帮你把所有数据结构串在一个完整的框架里。不过这类书偏推导和证明不建议初学者直接硬啃最好在刷了几十道题之后、对常见算法已有一定直觉时再回头精读那时你会突然发现很多当初记住的操作都有了深刻的理论依据。此外配套一本实战题解的笔记类书籍是很有必要的。这类书通常把题目按题型分类给出多种解法和复杂度分析非常契合笔记复习的需求。但注意不要只看答案要自己先写完再看题解对照差异。7.4 避坑清单不要只刷简单题简单题容易带来虚假的成就感但不锻炼算法设计能力。适当上难度才会真正进步。不要贪多求全每天刷十道抄答案的题不如每天认认真真搞明白两道题。质量永远大于数量。不要忽视复杂度分析能跑通只是底线能说清楚时间复杂度、空间复杂度才算真的掌握。我见过太多人写 O(n^2) 的代码去解 O(n) 要求的题思路对了但因为没分析复杂度而推倒重来。不要跳步从暴力解到最优解的过程一定要完整走一遍。暴力解的价值在于给你一个正确的起点最优解的价值在于让你理解优化的方向。笔记里把两条路径都写上远比只记最优解有价值。8. 我的个人体会与最终建议说到底数据结构和算法这门功课没有太多灵丹妙药靠的是理解 重复 应用三个环节的持续循环。笔记扮演的核心角色就是让这三个环节形成一个闭环理解的东西记录成逻辑链路重复的东西通过复习机制实现应用的东西通过三遍刷题法检验。我在整理笔记的过程中最大的体会是笔记的价值不在多而在你能不能通过自己的语言把逻辑讲清楚。如果你合上笔记能用自己的话把为什么用哈希表为什么快慢指针能判环为什么 DP 要这么初始化讲明白那这份笔记就是真正属于你的否则只是一堆搬运过来的文字而已。最后再分享一个小技巧我会在每章笔记的开头单独留一页记录自己对这一章最初的困惑和后来的理解变化。三个月后再翻回去看你会明显感觉到自己的思维在升级。这种曾经不懂、后来懂了的轨迹是任何现成教材都给不了你的成就感也是坚持整理笔记的最大动力。希望这篇内容能帮你在数据结构和算法的路上少走一些弯路。不用急着追求刷了多少题先把笔记体系建起来把每一个模板和结构真正吃透剩下的就是时间和耐心的问题了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑