
寒假之前我其实已经断断续续刷过不少算法题书也啃过几本但状态一直很迷。看见简单题能写碰上中等偏上就开始瞎试一没思路就翻题解翻完又觉得自己会了合上答案还是不会。后来听人推荐把 Acwing 的算法基础课完整跟了一遍才真正把脑子里那些散乱的知识点串成了一张网。这篇东西就当是给同样想在寒假系统学算法的朋友留的一份路书包含我对这门课的理解、实际学习节奏、踩过的坑以及几个核心专题的实操要点。先说清楚这门课解决什么问题。Acwing 算法基础课不是那种“三分钟看完一个技巧”的短视频课它是按专题铺开的完整体系把算法竞赛和面试常考的基础内容拆成排序、二分、数据结构、搜索、图论、动态规划、贪心这些大块每个大块配模板、例题和讲解。适合三类人准备蓝桥杯或 CSP 这类竞赛的在校生想补算法底子去面开发岗的人还有刷 LeetCode 总是卡在思路断层、想系统搭框架的人。零基础也可以跟但最好先会写基本的 C 或 Java能看懂数组、循环、递归就行。1. 为什么选 Acwing 算法基础课作为寒假主攻线1.1 算法学习最大的坑是没有反馈闭环我身边不少人学算法是这样学的打开收藏夹里的网课听老师讲完一种方法觉得自己懂了然后去刷题刷两道就停过两周全忘光。这个流程缺了两样关键东西一是自己动手把模板敲出来的环节二是有梯度的练习题反馈。Acwing 基础课的好处是它把这三个环节绑在一起。每节课有讲义讲完模板之后配套大量题目题目的难度从模板直接套用到需要自己变形跨度设置得很合理。而且这些题不是随便拼凑的是尽量贴近竞赛真题出题思路的做起来不会产生“学了屠龙技但不会用”的错觉。对我来说真正改变学习效率的是它要求“敲完代码必须提交并 AC”的过程这逼着你去面对细节而不是停留在脑内模拟。1.2 基础课到底覆盖哪些范围如果只用一句话介绍这门课我会说它是用竞赛标准重新组织了一遍大学算法课的内容。我的实际体验是它的专题划分比学校课程更贴近实战而且每个专题都有“模板变形套路”三层。基础语法快速过主要帮还不太熟 C 的同学排序与二分快排、归并、整数二分、浮点二分高精度大数加减乘除前缀和与差分一维、二维双指针与位运算常见优化手段数据结构链表、栈、队列、单调栈、单调队列、KMP、Trie、并查集、堆、哈希表搜索DFS、BFS、剪枝、双向广搜、IDA*图论最短路、最小生成树、拓扑排序、二分图匹配数学数论、组合数、容斥、博弈论、概率期望动态规划背包、线性、区间、状态压缩、树形、记忆化搜索贪心常见贪心模型时空复杂度分析这个容易被忽略但实际很重要我自己之前已经会一些算法但知识点窝在一起遇到题目不知道该套哪个。基础课把这些知识一块块拆开每块单独练到熟再混合到一起效果比我自己刷题好太多。1.3 寒假为什么是学算法的黄金窗口算法学习最怕碎片化。一个专题需要连续投入几小时才能形成手感如果每天只有二十分钟基本都浪费在重新回忆代码和思路上了。寒假是少有的整块时间我会建议把它当成一门“三学分的课”来排每天固定三小时上午一小时看视频看书剩下两小时敲代码和提交。为什么强调固定时间因为算法这东西刚上手时有明显的“手生期”特别是从看代码到自己写中间有条不小的沟。固定时间能帮你熬过最难受的那一两周等正反馈来了之后后面就容易坚持了。2. 基础课学习路线的核心思路2.1 先搭算法框架再抠技巧很多人学算法是看见一个技巧觉得好用就学一个结果脑子里全是树叶没有树干。基础课给的就是树干。我的建议是刷题时拿个本子记专题索引看到题目先判断属于哪个考点再决定用什么思路。比如看到“最短路径”先想是有负权还是无负权然后决定 Dijkstra、SPFA 还是 Floyd。这套“先分类再套模板”的思维比背代码重要得多。因为题目千变万化但考点就那么几十个能把新题归到已知考点基本就成功了一半。2.2 模板代码必须自己敲不能只看这是我在这个寒假最大的教训。看视频的时候觉得老师写得挺简单合上书自己写就会发现什么叫“眼睛会了手不会”。所以我的方法是每学完一个模板先合上书自己敲一遍再对照老师的写法找差异然后把模板默写到本地笔记里。拿二分来举例。很多人以为二分简单其实边界最容易写错。整数二分的两个模板查找左边界和查找右边界mid 的取法不一样循环条件不一样。如果只是看懂了原理就跳过真到题目里就会死循环或者答案差一位。我记得自己刚开始练二分时按背下来的模板写一道题本地跑出来对一提交就错。后来才发现自己漏了写while (l r)而不是while (l r)。这种细节只有自己动手踩一遍才会记住。2.3 我建议的八周节奏安排内容量不小但是合理安排时间后一个寒假基本能过完第一轮。下面是可复制的节奏表格周次主题目标第一周排序、二分、高精度、前缀和与差分把所有基础模板默写出来第二周双指针、位运算、栈与队列、单调栈、单调队列能独立使用模板分析题目第三周KMP、Trie、并查集、堆、哈希表重点搞懂 KMP 的 next 数组和并查集路径压缩第四周DFS、BFS、剪枝、双向广搜通过搜索专题练习递归思维能自己剪枝第五周最短路、最小生成树、拓扑排序、二分图搞懂 Dijkstra、SPFA、Bellman-Ford 的适用场景第六周数论、组合数、容斥原理、博弈论会快速求质数、逆元能读懂组合计数代码第七周背包、线性 DP、区间 DP、状态压缩等把背包九讲吃透其他 DP 过一遍第八周贪心、复杂度分析、真题套题用往年题检验重点查漏补缺这个表不用百分百照搬因为每个人基础不一样。但有个原则值得坚持宁可一个专题慢一点也不要为了赶进度把题目只看不敲。3. 核心算法专题实操要点3.1 排序与二分看似简单处处是坑排序里最值得掌握的是快速排序和归并排序。快速排序的平均复杂度是 O(n log n)但递归深度在某些数据下可能退化到 O(n)所以竞赛里有时会用手写堆排或直接使用sort函数。归并排序的稳定性和用于求逆序对的性质则是它被单独拎出来考察的原因。归并排序求逆序对的核心就是在两个有序数组合并时看到右侧数组有元素比左侧当前元素小就把左侧剩余元素数量累加进答案。这个操作本身不复杂但第一次写的时候很容易漏掉res mid - i 1这条语句导致答案少了一大截。二分则更考验细心。我的建议是直接背熟两个模板不要每次现推。查找左边界用while (l r) { int mid l r 1; if (check(mid)) r mid; else l mid 1; }查找右边界用while (l r) { int mid l r 1 1; if (check(mid)) l mid; else r mid - 1; }第二个模板里为什么mid要加 1因为如果l和r相差 1mid (lr)/2等于l当check(mid)为真时l不变就会死循环。补上1之后mid 偏向右侧才能保证区间严格缩小。这个细节是我当时卡了半小时才想明白的值得记录下来。3.2 数据结构专题栈、队列、KMP 与并查集栈和队列本身不算难难的是它们的“单调”变体。单调栈典型的应用是“找下一个更高的元素”这类问题比如给定一排身高问每个人右边第一个比他高的人是谁。做法是维护一个从栈底到栈顶递减的栈遍历每个元素时把栈里所有比它小的元素弹出弹出的元素的下一个更高元素就是当前元素。单调队列则常用于滑动窗口极值。比如固定窗口大小求最大值需要双向队列队头存当前窗口最大值新元素进来时从队尾弹出所有比它小的元素。这里的重点不是怎么写队列入队出队而是理解“那些被弹出的元素为什么以后永远不会成为答案”想通这个代码就顺了。KMP 算法是基础课里劝退率比较高的一个点。它的核心不是快速匹配本身而是next数组的构建next[i]表示模式串前i个字符中最长的相等前后缀长度。这样当匹配失败时主串指针不用回退模式串向右滑到下一个可能匹配的位置。我建议初学时不急着背代码先手动模拟一遍字符串从下标0到n-1的next数组生成过程模拟上两遍再回来看代码就不会懵了。并查集是竞赛里出镜率极高的数据结构核心操作就两个查找根节点和合并集合。路径压缩是必须写的否则每次查找的复杂度可能退化成链式 O(n)。int find(int x) { if (p[x] ! x) p[x] find(p[x]); return p[x]; }路径压缩直观理解就是查一个节点时顺手把沿途所有节点直接挂到根节点下面。这样再查它们时就是 O(1) 了。配合按秩合并并查集复杂度接近常数级别。3.3 图论专题最短路与最小生成树图论部分专题多、模板多但核心其实是理解不同算法适用条件的差异。如果题目没有负权边优先用 Dijkstra因为它的复杂度稳定是 O(m log n)如果有负权边但保证没有负环用 SPFA 或 Bellman-Ford如果要求任意两点间最短路且点数少用 Floyd 的三层循环。我见过很多人把 Dijkstra 和 SPFA 混用其实没必要。SPFA 虽然代码短但在一些构造数据上会被卡成 O(nm)甚至在竞赛里直接超时。所以我的经验是能上 Dijkstra 就不要用 SPFA除非题目明确存在负权边。最小生成树方面Kruskal 算法因为实现简单是我在实战中的首选。它的思路是把所有边按权值排序从小到大用并查集判断两个端点是否已经连通如果不连通就加入答案。复杂度是 O(m log m)主要开销在排序。Prim 算法适合稠密图但是竞赛和面试场景里Kruskal 出场机会更多。二分图的染色法和匈牙利算法也值得花时间。判断一个图是不是二分图用 DFS 染色即可求最大匹配数则用匈牙利算法它的核心思想是“让之前匹配的人重新找下家”尽量腾出空位给新来的人。这个算法看起来像贪心但里面其实有个递归回溯的过程不写一遍很难体会。3.4 动态规划与贪心最难啃的两块动态规划是把我卡住时间最长的一章。刚开始我总想把状态转移方程憋出来但没搞清楚状态本身是什么。后来我给自己的规定先回答三个问题再动手写代码。问题一是“状态表示什么”问题二是“状态从哪里来”问题三则是“初始值是什么”。这三个问题想清楚大部分 DP 题就能解了。拿 01 背包来举例。状态f[i][j]表示前i件物品中选总体积不超过j时能获得的最大价值。转移方程是f[i][j] max(f[i-1][j], f[i-1][j-v[i]] w[i])前一项表示不选第i件后一项表示选。然后你会发现第二维可以压成一维但必须倒着遍历j否则同一个物品会被选多次。这个“为什么倒着”就是 DP 的细节所在。贪心算法看着比 DP 简单套路是排序后选当前最优但难在证明贪心是正确的。我的建议是别怕证明常见的证明方法有交换论证法、反证法和边界情况分析法。如果不想深究证明至少要背熟几种经典的贪心模型比如区间选点、区间分组、哈夫曼编码、活动安排等。4. 实操过程与核心环节实现4.1 从“看懂”到“写对”的完整案例拿 01 背包这题当例子因为它小、典型又能一次性解释清楚模板、边界和空间优化三个层面。首先是朴素写法用一个二维数组#include iostream using namespace std; const int N 1010; int v[N], w[N]; int f[N][N]; int main() { int n, m; cin n m; for (int i 1; i n; i) cin v[i] w[i]; for (int i 1; i n; i) { for (int j 0; j m; j) { f[i][j] f[i - 1][j]; if (j v[i]) f[i][j] max(f[i][j], f[i - 1][j - v[i]] w[i]); } } cout f[n][m] endl; return 0; }这个版本没问题但空间复杂度 O(n*m) 在 n、m 到 1e4 时就不太行了。观察转移方程会发现f[i]只依赖f[i-1]所以可以压成一维。关键是一维数组的循环顺序for (int i 1; i n; i) { for (int j m; j v[i]; j--) { f[j] max(f[j], f[j - v[i]] w[i]); } }为什么 j 要倒着因为正着遍历时f[j - v[i]]已经被这一轮更新过了此时f[j - v[i]]代表的已经不是“前 i-1 件物品”的状态而是“前 i 件物品”的状态相当于同一个物品反复被选入。倒着遍历时f[j - v[i]]还没被本轮的更新影响存的是上一轮的旧值正好符合 01 背包的语义。这种空间优化看一遍觉得是“巧合”自己推导一次才知道本质上是状态依赖的方向决定了遍历方向。4.2 边界条件和初始化AC 与 WA 的分水岭做了几十道题之后我总结出提交出错的常见位置按出现频率从高到低排数组越界最常见的是开数组不够大比如题面说n 100000有些人直接开1010。另一个是j从 0 开始遍历但没加if (j v[i])判断导致访问负数下标死循环多见于二分边界写错或者 DFS 里忘了标记已访问节点答案差一比如题目要求下标从 1 开始代码里从 0 遍历或者mid下取整导致右边界漏掉数据类型溢出求逆序对、求最长上升子序列和时int可能不够用记住用long long我自己栽得最狠的一次是写并查集合并时把p[find(a)] find(b)写成了find(a) find(b)编译直接报错。这种“看起来一样实际根本不能赋值”的错误只有亲手写一遍才会记住。4.3 用调试定位问题而不是瞎猜遇到 Wrong Answer我建议先不要急着改代码瞎交。最快的是在本地用样例输入跑一遍如果输出和预期不一致就在关键位置打印中间变量。比如二分模板可以在循环里打印l, r, mid三个值看看区间到底是怎么收缩的。单调队列则打印队列里存的下标和对应的值检查队头是否在窗口外。有一次我写单调队列题本地样例全过提交就超时。一打印发现队列存的是值而不是下标结果窗口移动时无法判断过期元素队列里堆了一堆无效数据复杂度退化成了 O(n^2)。换成存下标每次循环前先按q[hh] i - k 1判断过期就把性能救回来了。Debug 的时候能看到过程比靠猜快太多。5. 常见问题与避坑指南5.1 学完就忘正常吗太正常了。算法这种东西一周不看手就生。我解决“遗忘”问题的办法不是赌自己的记忆力而是建立了一份个人模板库按专题分类存好代码。不是网上抄的是自己默写过一遍后整理的。模板库旁边还写了一行注释这个模板用在什么场景边界是什么当初写错在哪。每次刷题前先翻对应的模板不看代码只在脑子里过一遍思路如果卡住了再看一眼注释。这样刷题的过程本身就在复习模板比单独找时间“背模板”更自然。5.2 刷题速度慢、不敢 AC 怎么办我发现很多初学者有个误区一道题盯了两小时没做出来就觉得自己很笨然后翻题解翻完继续做下一道。其实这才是学算法最慢的方式。正确的做法是给自己定个时限简单题 20 分钟中档题 40 分钟。到了时限还没思路就去看题解看懂后合上书把代码完整默写一遍然后自己重新把思路讲一遍。这一步是关键如果讲不出来说明没真懂。用这个策略我一天能消化三到四道新题虽然看起来速度不快但每道题都是真正过了一遍脑子的。比起一天刷十道但十道都记不住划算得多。5.3 Acwing 基础和 LeetCode 怎么搭配有人问刷 Acwing 基础课够不够要不要再刷 LeetCode。我的看法是两者目的不同。Acwing 基础侧重竞赛常用技巧和模板而 LeetCode 的题目更偏面试场景很多题考的其实是二分的边界、动态规划的细节。所以可以先把 Acwing 的基础课过一遍把框架搭起来然后用 LeetCode 每日一题来练手。我自己的节奏是上午跟着基础课学模板、做课后题下午用 LeetCode 检验一下优先挑和今天专题相关的题。这样两边互为补充实战感会强很多。5.4 最后几点备考小技巧每天结束前花十分钟把今天的错题记到备忘录里周末统一重新做一遍而不是留到考前临时看掐时间做套题尤其是蓝桥杯或 CSP 的往年真题提前适应比赛的节奏学累了就散散步别硬坐。算法题有时候就是需要脑子放空一下回头就能看到思路我个人在实际操作中的体会是寒假学算法的关键不在于你多聪明而在于你有没有一套固定的流程把每个知识点真正过手。Acwing 基础课提供了一条清晰的主线但真正把它内化成自己能力的还是每天雷打不动的那几个小时和那些 AC 之后又重写一遍、总结一遍的笨功夫。如果你正给自己安排寒假计划不妨就把这门课当成一个阶段性的训练营把该踩的坑踩一遍把该背的模板背熟等寒假过完再回去看之前卡住的题你会发现它们突然就变简单了。