1. 为什么"算法基础课"这四个字值得反复刷很多人第一次听到"算法基础课"这五个字,第一反应是"这不就是给刚入门的人看的吗"。我一开始也这么想,直到自己刷完一遍、又回头刷第二遍、第三遍的时候,才发现这门课真正的价值根本不在"入门"两个字上,而在于它把一整套算法思维的骨架给你搭好了。我见过太多人学算法的路径是这样的:先找一本厚厚的算法书,翻两页看到数学公式就劝退;然后转去看各种零散的题解视频,东一榔头西一棒子,刷了两百道题还是遇到新题就懵。问题出在哪?出在没有体系。你脑子里装的是一道道孤立的题,而不是一类类问题的通用解法。算法基础课解决的就是这个问题。它把算法竞赛和面试里最核心的那批知识点——排序、二分、高精度、前缀和与差分、双指针、位运算、离散化、区间合并、链表、栈与队列、单调栈与单调队列、KMP、Trie、并查集、堆、哈希表、图论基础、最短路、最小生成树、二分图、数论、动态规划、贪心——按照从易到难、从基础到进阶的顺序串成一条线。你跟着这条线走一遍,脑子里就有了地图,知道每个知识点在哪个位置、和哪些知识点有关联、遇到什么类型的题该往哪个方向想。这篇文章不是给你复述课程目录的,那种东西你打开课程页面就能看到。我想聊的是:这门课到底该怎么刷才有效,哪些章节是必须反复啃的硬骨头,哪些地方新手最容易卡住,以及我自己在刷的过程中总结出来的一些"反常识"的经验。不管你是刚接触算法的新手,还是刷了几百道题想回头补基础的老手,下面这些内容应该都能帮你少走一些弯路。2. 刷课之前必须想清楚的三个问题2.1 你的目标决定了你的刷课策略同样是刷算法基础课,目标不同,刷法完全不一样。我把常见的目标分成三类,你可以对号入座:目标类型典型人群刷课重点建议投入时间打算法竞赛在校学生、竞赛选手全部章节精刷,重点在图论、数论、DP3-6个月,每天2-4小时准备技术面试求职者、转行人员排序、二分、双指针、哈希、DP、图论基础2-3个月,每天1-2小时补计算机基础工作多年的开发者按兴趣选章节,重点理解思想而非实现灵活,碎片时间即可我见过最典型的错误就是:一个准备面试的人,花了两周时间死磕KMP的next数组推导,结果面试根本没考字符串。不是说KMP不重要,而是投入产出比的问题。面试里最高频的是数组、字符串、哈希、双指针、二分、简单DP和二叉树相关的内容,这些才是你应该优先吃透的。反过来,如果你是想打竞赛,那就不能挑食。竞赛的题目往往是把多个知识点揉在一起考,你缺了任何一块都可能卡住。比如一道题可能同时用到离散化、前缀和和二分,你少会一个就做不出来。2.2 语言选择:别在这件事上纠结超过十分钟课程支持C++和Java两种语言。我的建议很直接:打竞赛,选C++。没别的原因,就是快。STL的vector、priority_queue、unordered_map这些容器在竞赛里用起来太顺手了,而且C++的常数小,同样的算法C++能过、Java可能就TLE(超时)。准备面试,选你未来工作要用的语言。面试官更关心你的思路,语言只是表达工具。如果你投的是Java岗位,那就用Java刷,顺便熟悉Java的集合框架。纯粹想学算法思想,选你写着最舒服的语言。思想是跨语言的,用Python写伪代码级别的实现也完全没问题。注意:不要因为"听说C++快"就硬着头皮用一门自己不熟的语言。语言不熟会严重拖慢你的刷题速度,而且会让你把注意力放在语法上而不是算法本身上。我见过有人用C++刷了一个月,还在为指针和引用的问题debug,这就本末倒置了。2.3 前置知识:你需要多少数学和编程基础这是很多人关心的问题。我的答案是:编程基础只需要会写循环、数组、函数就够了;数学基础只需要高中水平。课程里涉及到的数学知识主要是:质数判断、约数、欧拉函数、快速幂、扩展欧几里得、中国剩余定理、高斯消元、组合计数、容斥原理、博弈论。听起来很吓人对吧?但实际上每个知识点课程都会从最基础的定义讲起,你不需要提前学数论,跟着课程走就行。真正需要你提前具备的,是基本的代码调试能力。你得会看编译错误、会用打印大法找bug、能理解数组越界和整数溢出是怎么回事。这些能力不是看视频能学会的,只能通过自己动手写、自己踩坑来积累。3. 课程章节的难度分层与刷课节奏3.1 把二十多个章节分成四个梯队课程章节很多,如果平均用力,你会发现自己一直在舒适区里打转,或者一直在硬骨头面前卡死。我的做法是把章节按难度和重要性分成四个梯队,不同梯队用不同的策略。第一梯队:基础中的基础(必须滚瓜烂熟)排序、二分、高精度、前缀和与差分、双指针、位运算、离散化、区间合并。这些章节的特点是:代码短、思想直观、但应用极其广泛。排序和二分是后面几乎所有算法的基石,前缀和与差分是处理区间问题的万能工具,双指针是优化暴力的第一思路。这些章节的模板代码你必须做到闭着眼睛都能敲出来,不能有任何犹豫。我自己的标准是:第一梯队的每个模板,我都能在3分钟内从零写出来并且一次通过。如果你做不到,就反复练,练到形成肌肉记忆为止。第二梯队:数据结构入门(理解为主,实现为辅)链表、栈与队列、单调栈与单调队列、KMP、Trie、并查集、堆、哈希表。这些章节的核心是理解每种数据结构解决了什么问题。比如单调栈解决的是"找每个元素左边/右边第一个比它大/小的元素",并查集解决的是"动态连通性"问题,Trie解决的是"字符串前缀匹配"问题。实现细节当然也要掌握,但更重要的是看到一个问题能反应过来该用什么结构。这个反应速度是靠刷题量堆出来的,没有捷径。第三梯队:图论与数论(硬骨头,需要反复啃)图论基础、最短路、最小生成树、二分图、数论。这是整个课程里最硬的部分。图论的难点在于建模——给你一个实际问题,你要能抽象成图论模型,判断是最短路还是最小生成树还是二分图匹配。数论的难点在于推导——很多结论需要你自己推一遍才能真正理解,光背模板没用。我的建议是:这两个模块不要赶进度,一个章节可以花一周甚至两周。每道例题都要自己先想,想不出来再看题解,看完之后合上题解自己再写一遍。第四梯队:动态规划与贪心(思想性最强)动态规划、贪心。DP是公认的最难章节,没有之一。它的难不在于代码,而在于状态定义和转移方程的设计。同一个问题,状态定义不同,复杂度可能差一个数量级。贪心则难在证明——你怎么确定你的贪心策略是对的?这两个章节我没什么速成的办法,只能靠大量练习来培养感觉。但有一个技巧:做完每道DP题之后,问自己三个问题——状态表示的是什么?转移是怎么来的?边界是怎么处理的?把这三个问题想清楚了,这道题才算真正做完。3.2 刷课节奏:不要追求"刷完",要追求"刷透"我见过太多人把刷课当成看剧,一集接一集地看,看完一章就急着看下一章,结果看到DP的时候发现前面的全忘了。正确的节奏应该是:每学完一个章节,至少做10-20道对应的练习题,然后再进入下一章。练习题不用非得是课程配套的,你可以在各大刷题平台上按标签筛选。具体的时间分配,我建议是这样的:看视频/读讲义:占总时间的30%自己敲模板代码:占总时间的20%做练习题:占总时间的50%很多人把比例搞反了,花80%的时间看视频,20%的时间做题,结果就是"一看就会,一写就废"。提示:每学完一个章节,隔三天再回头默写一遍模板。如果还能默写出来,说明真的记住了;如果忘了,说明需要再练。这个"间隔重复"的方法比连续刷十遍有效得多。4. 那些让我卡了很久的硬骨头章节4.1 KMP:从"背模板"到"真理解"的转折点KMP是我第一个真正卡住的章节。当时我把模板背下来了,也能AC对应的模板题,但一到变形题就懵。后来我才意识到,我根本没理解next数组的含义。next数组的本质是:对于模式串的每个位置,记录它之前的最长相等前后缀的长度。这个"最长相等前后缀"就是KMP的核心。当你匹配失败的时候,你不需要从头开始,而是可以利用已经匹配的信息,把模式串往后滑动到下一个可能匹配的位置。理解了这个之后,很多变形题就迎刃而解了。比如"求字符串的最小循环节",其实就是用字符串长度减去next数组最后一个值;"求每个前缀出现的次数",就是沿着next数组往回跳。我的建议是:不要死记模板,自己画图推导一遍next数组的生成过程。拿一个具体的字符串,比如"ababab",手动算一遍它的next数组,然后再对照代码看。这个过程花不了半小时,但能让你真正理解KMP。4.2 动态规划:状态定义是唯一的门槛DP的难点只有一个:状态定义。一旦状态定义对了,转移方程往往是很自然的。我总结了一个定义状态的通用思路:问自己"我要决策什么,决策依赖什么信息"。比如背包问题,你要决策"第i个物品选不选",决策依赖于"当前还剩多少容量",所以状态就是"前i个物品,容量为j时的最优值"。再比如最长上升子序列,你要决策"第i个元素接在哪个元素后面",决策依赖于"前面以某个元素结尾的上升子序列的长度",所以状态就是"以第i个元素结尾的最长上升子序列长度"。这个思路能解决大部分DP问题的状态定义。当然,有些问题的状态定义比较巧妙,需要你做过类似的题才能想到。这就是为什么DP需要大量练习——你见过的状态定义越多,遇到新问题时能联想到的可能性就越大。4.3 图论建模:从"这题在说什么"到"这是个图论题"图论最难的从来不是算法本身,Dijkstra、SPFA、Kruskal这些算法的模板都很固定,难的是你怎么知道这道题要用图论做。我举几个典型的建模场景:最短路建模:题目里出现"从A到B的最少步数/最小花费/最短时间",而且状态之间有明确的转移关系,大概率是最短路。把每个状态看成图上的一个点,状态之间的转移看成边。最小生成树建模:题目里出现"用最小的代价把所有点连通",而且边是双向的、有权的,大概率是最小生成树。二分图建模:题目里出现"把一堆东西分成两组,组内有冲突"或者"匹配问题",大概率是二分图。这些建模能力不是看视频能学会的,只能通过做题来积累。我的建议是:每做完一道图论题,都问自己"这道题的关键建模步骤是什么",然后把这个建模思路记下来。积累多了,你看到新题就会有感觉。4.4 数论:推导比结论重要数论章节里有很多结论性的东西,比如欧拉定理、中国剩余定理、高斯消元。很多人只记结论,不记推导,结果遇到变形题就废了。我的建议是:每个结论都自己推一遍。比如欧拉定理,你要理解为什么a的φ(n)次方模n等于1,这个推导过程用到了剩余系的乘法封闭性。理解了推导,你才能知道这个定理的适用条件是什么、什么时候不能用。再比如扩展欧几里得,它的核心是裴蜀定理:ax+by=c有整数解当且仅当gcd(a,b)能整除c。理解了这一点,你才能知道为什么可以用扩展欧几里得求逆元、解同余方程。5. 模板代码的正确打开方式5.1 模板不是用来背的,是用来"长"在手上的很多人对模板代码有误解,觉得背下来就行了。但我的经验是:背下来的模板,一到关键时刻就会忘;只有自己推导过、写过的模板,才能真正长在手上。我的做法是这样的:每学一个算法,先理解它的思想,然后自己尝试写一遍,写不出来再看模板,看完之后合上模板再写一遍。这个过程可能要重复三四次,但每次重复你都会对算法有新的理解。比如快速排序,第一遍你可能只是照着模板敲;第二遍你会思考为什么基准值要随机选;第三遍你会理解为什么用do-while而不是while;第四遍你就能自己写出各种变体了。5.2 几个必须练到"肌肉记忆"的模板根据我的经验,下面这些模板是必须练到闭着眼睛都能写出来的:快速排序和归并排序。这两个是分治思想的代表,后面的很多算法都建立在它们之上。特别是归并排序,它的"合并两个有序数组"的思想在求逆序对、求区间和等问题里反复出现。整数二分和浮点二分。二分的边界处理是新手最容易出错的地方。我建议统一用一套自己习惯的写法,比如"左闭右开"或者"左闭右闭",然后所有二分题都用这套写法,不要每次换一种。前缀和与差分。一维和二维的都要会。前缀和用于快速求区间和,差分用于快速做区间修改。这两个是处理区间问题的万能工具。Dijkstra和SPFA。最短路的两大主力算法。Dijkstra用于非负权图,SPFA用于有负权边的图。两个都要会写堆优化版本。并查集。路径压缩和按秩合并都要会。并查集的代码很短,但应用极其广泛。KMP。虽然面试不一定考,但KMP的思想(利用已匹配信息避免重复计算)在很多地方都有体现。5.3 模板的常见坑即使是模板代码,也有一些容易踩的坑。我列几个最常见的:算法常见坑正确做法快速排序基准值选端点导致最坏O(n²)随机选基准值或选中点二分查找边界更新写错导致死循环统一用左闭右开或左闭右闭前缀和数组下标从0开始导致越界前缀和数组从1开始,a[0]=0Dijkstra忘记标记已访问导致重复入队出队时检查是否已访问并查集忘记路径压缩导致退化find时顺便压缩路径KMPnext数组下标含义搞混明确next[i]表示前i个字符的最长相等前后缀注意:这些坑我都踩过,而且不止一次。每次踩坑之后,我都会在代码注释里写清楚"这里为什么这么写",下次再写的时候就不会错了。6. 从"看懂"到"写对"的练习方法6.1 三遍刷题法我总结了一套"三遍刷题法",对我自己非常有效:第一遍:看题解,理解思路。不要一上来就自己硬想,那样效率太低。先看题解,理解这道题的核心思路是什么、用了什么算法、关键步骤是什么。第二遍:合上题解,自己写。看完题解之后,合上题解,自己从头写一遍。这一遍你可能会卡住,卡住的时候不要急着看题解,先自己想一想,实在想不出来再看。第三遍:隔几天再写一遍。隔三天到一周,再回头把这道题写一遍。如果还能写出来,说明你真的掌握了;如果忘了,说明需要再练。这个方法的核心是主动回忆。看题解是被动输入,自己写是主动输出,隔几天再写是间隔重复。三者结合,记忆效果最好。6.2 错题本的正确用法我强烈建议你准备一个错题本,但不要只是抄题目和答案。我的错题本里记录的是这些东西:这道题我为什么错了:是思路错了,还是边界处理错了,还是模板写错了?正确的思路是什么:用一两句话概括核心思路。这道题的关键步骤是什么:哪一步是我想不到的?有没有类似的题:这道题和之前做过的哪道题类似?这样记录的好处是:复习的时候你不是在重新做一遍题,而是在回顾自己的思维盲区。这比盲目刷题有效得多。6.3 不要害怕看题解很多人觉得看题解是"作弊",非要自己硬想。我的观点是:看题解不丢人,丢人的是看完题解就忘了。算法学习本来就是一个"模仿-理解-创造"的过程。你先看别人怎么做的,理解别人的思路,然后自己模仿着做,最后才能自己创造新的解法。没有前面的模仿和理解,你不可能凭空创造出解法。关键是看完题解之后要自己写一遍,而且要隔几天再写一遍。只要做到这两点,看题解就是高效的学习方式。7. 刷完基础课之后的路怎么走7.1 从模板题到综合题的过渡刷完基础课之后,你会发现一个尴尬的现象:模板题都会做,但一遇到综合题就懵。这是因为基础课的题目大多是单一知识点的,而综合题往往需要多个知识点组合。过渡的方法很简单:开始刷综合题,从简单的开始。比如一道题可能同时用到前缀和和二分,你先分别想清楚每个部分怎么做,再把它们组合起来。一开始可能会很慢,但做多了就有感觉了。我建议的过渡路径是:先刷各平台的"简单"难度的综合题,然后逐渐过渡到"中等",最后再挑战"困难"。不要一上来就刷难题,那样只会打击信心。7.2 进阶方向的选择刷完基础课之后,你可以根据自己的目标选择进阶方向:打竞赛:继续学进阶算法,比如线段树、树状数组、AC自动机、后缀数组、网络流、计算几何等。准备面试:开始刷面试高频题,重点练习数组、字符串、链表、二叉树、DP、设计题。提升工程能力:把学到的算法应用到实际项目中,比如用图论做路径规划、用DP做资源分配、用并查集做连通性分析。不管选哪个方向,基础课打下的底子都是有用的。算法思想是通用的,你在这里学到的分治、贪心、DP、图论建模,在任何一个方向都用得上。7.3 保持手感:定期回顾算法这东西,不用就会忘。我自己的做法是:每周至少做两道题保持手感,每个月回头复习一个章节的模板。复习的时候不用重新做所有题,只需要默写模板、做一两道典型题就够了。关键是不要让手生。提示:如果你已经工作了,时间不多,可以只保持每周一道题的节奏。但一定要坚持,一旦断了超过一个月,再捡起来就很痛苦了。8. 我踩过的那些坑和总结的经验8.1 不要追求刷题数量我刚开始刷题的时候,特别在意"刷了多少道"。看到别人刷了500道,我就想刷600道。结果就是:刷过的题过两周就忘了,遇到新题还是不会。后来我改变了策略:不再追求数量,而是追求"每道题都真正搞懂"。一道题做三遍,比三道题各做一遍效果好得多。刷题数量从500降到200,但我的实际水平反而提高了。8.2 不要跳过"简单"的章节很多人觉得排序、二分、前缀和这些太简单了,随便看看就跳过了。但我的经验是:这些"简单"章节恰恰是最重要的。排序和二分是后面几乎所有算法的基石,前缀和与差分是处理区间问题的万能工具。这些章节的模板你必须练到滚瓜烂熟,不能有任何含糊。我见过太多人因为二分边界写错而WA(答案错误),因为前缀和下标搞混而RE(运行时错误)。8.3 不要一个人死磕遇到卡住的地方,不要一个人死磕超过半小时。超过半小时还没思路,就去看题解或者问别人。死磕的代价是巨大的:你会浪费大量时间,而且容易产生挫败感。当然,看题解之前要先确保自己真的理解了题目,知道自己卡在哪一步。带着问题去看题解,效果比盲目看要好得多。8.4 写代码之前先写伪代码这是我后来养成的一个习惯:写代码之前,先用中文把思路写一遍。比如:1. 读入n和数组a 2. 对a进行排序 3. 用双指针i=0, j=n-1 4. while i < j: 5. 如果a[i]+a[j] == target,返回 6. 如果a[i]+a[j] < target,i++ 7. 否则j--这个伪代码写出来之后,再翻译成实际代码,出错的概率会大大降低。而且如果思路有问题,在伪代码阶段就能发现,不用等到debug的时候才发现。8.5 重视边界情况算法题最容易出错的地方就是边界情况。我总结了几种常见的边界:空输入:n=0的情况单元素:n=1的情况全部相同:所有元素都相等的情况极值:最大值、最小值、负数的情况溢出:中间结果超过int范围的情况每次写完代码,我都会用这几组边界数据测一遍。这个习惯帮我避免了很多WA。8.6 学会看别人的代码刷题平台上每道题都有很多人的提交记录,我建议你做完一道题之后,去看看排名靠前的代码。你会发现很多巧妙的写法,比如用位运算代替除法、用数组代替哈希表、用递归代替循环。看别人的代码不是为了抄,而是为了拓宽思路。你可能会发现,原来这个问题还可以这样解。这些思路积累多了,你自己写代码的时候就会有更多的选择。9. 给不同阶段学习者的具体建议9.1 零基础新手:先跑通,再理解如果你完全没接触过算法,我的建议是:先跑通,再理解。不要一上来就试图理解每个算法的证明和推导,那样你会被劝退。先跟着课程把代码敲一遍,让它跑起来,看到正确的结果。然后慢慢去理解为什么这样写是对的。比如快速排序,你先照着模板敲一遍,看到它能正确排序,然后再去理解分治的思想、基准值的作用、递归的过程。这个顺序比反过来要容易得多。9.2 有基础但不成体系:按章节查漏补缺如果你已经刷了一些题,但感觉知识点很散,我的建议是:按章节查漏补缺。先快速过一遍课程目录,看看哪些章节你是完全不会的,哪些是会用但不熟的,哪些是已经掌握的。然后重点攻克"会用但不熟"的章节,这些章节往往是你的薄弱环节。对于"完全不会"的章节,不要急着学,先确保前面的基础章节都掌握了。算法学习是有依赖关系的,前面的没学好,后面的很难理解。9.3 准备面试:重点突破高频考点如果你是为了准备面试,我的建议是:重点突破高频考点。根据我的经验,面试里最高频的算法考点是:数组和字符串操作、双指针、二分查找、哈希表、链表、二叉树遍历、简单DP、BFS/DFS。这些内容在基础课里都有覆盖,你需要做的是针对性地多练。至于KMP、数论、复杂图论这些,面试里出现的概率很低,可以暂时放一放。等你把高频考点都吃透了,再回头补这些也不迟。9.4 工作多年想补基础:带着问题学如果你已经工作多年,想补一下算法基础,我的建议是:带着问题学。不要像学生时代那样从头到尾刷一遍,那样太耗时了。你可以先想想自己工作中遇到过哪些和算法相关的问题,比如性能优化、数据处理、路径规划等,然后针对性地去学相关的章节。这样学的好处是:你有实际的应用场景,理解起来会更快,而且学完就能用上,记忆也更深刻。10. 一些零散但有用的心得10.1 关于学习时间算法学习最忌讳的就是"三天打鱼两天晒网"。每天学一小时,坚持三个月,比周末突击十小时效果好得多。因为算法学习需要持续的思维训练,断断续续的话,每次都要重新进入状态。我自己的习惯是:每天早上花一小时刷题,雷打不动。这一小时里,我不看手机、不刷消息,就专注做题。这一小时的效果,比我晚上断断续续学三小时还要好。10.2 关于学习环境找一个安静的环境,关掉所有通知。算法学习需要深度思考,任何打断都会让你重新进入状态。我一般会把手机调成静音,放在另一个房间。另外,准备一个顺手的IDE或者编辑器。我见过有人用记事本写代码,然后抱怨效率低。工具不顺手,会严重影响你的学习体验。10.3 关于心态算法学习是一个长期积累的过程,不要指望速成。你可能会遇到这样的情况:刷了一个月,感觉没什么进步;或者做了一道题,怎么都想不出来。这些都是正常的。我自己的经验是:进步往往不是线性的,而是阶梯式的。你可能连续几周都感觉在原地踏步,然后突然有一天,你发现自己能做出之前做不出的题了。这就是量变到质变的过程。所以,不要因为一时的挫败就放弃。坚持下去,你会看到自己的进步的。10.4 关于复习复习比学习更重要。我自己的复习节奏是这样的:每天:花10分钟默写一个模板每周:花一小时复习一个章节的典型题每月:花半天时间,把当月学的内容整体过一遍这个复习节奏看起来花了不少时间,但实际上它帮你省下了更多的时间——因为你不用反复重新学习已经忘掉的东西。10.5 关于刷题平台刷题平台不用贪多,选一个主流的、题目质量高的就行。重要的是把题目做透,而不是在多个平台之间来回切换。我自己的做法是:主刷一个平台,遇到特别好的题或者特别典型的题,再在其他平台找类似的题做对比。这样既能保证题目的质量,又能拓宽视野。10.6 关于代码风格从一开始就养成好的代码风格。变量命名要有意义,不要用a、b、c这种;关键步骤要写注释;代码结构要清晰,不要把所有逻辑都塞在一个函数里。好的代码风格不仅让代码更容易读懂,也让你在debug的时候更容易定位问题。我见过很多人因为变量命名混乱,debug的时候自己都看不懂自己写的代码。10.7 关于"刷不动"的时候每个人都会有"刷不动"的时候。这时候不要硬撑,可以换个方式:看看算法相关的科普视频,换个角度理解做一道简单的题找找信心出去走走,让大脑放松一下和同学/同事讨论一下,说不定会有新的思路硬撑只会让你越来越烦躁,效率越来越低。适当放松,反而能让你更快地恢复状态。10.8 关于"看懂了但写不出"这是最常见的问题。看题解的时候觉得"哦,原来这么简单",但自己写的时候就是写不出来。原因很简单:你只是看懂了,但没有真正理解。解决方法是:看完题解之后,合上题解,自己从头写一遍。写不出来就再看,看完再写,直到能独立写出来为止。这个过程可能很痛苦,但这是唯一有效的方法。10.9 关于"一题多解"很多题都有多种解法。比如最短路问题,可以用Dijkstra、Bellman-Ford、SPFA、Floyd等多种算法。我的建议是:至少掌握两种解法。掌握多种解法的好处是:你可以根据题目的特点选择最合适的算法。比如数据范围小的时候可以用Floyd,数据范围大的时候用Dijkstra;没有负权边的时候用Dijkstra,有负权边的时候用SPFA。10.10 关于"学完就忘"这是正常的,不用焦虑。人的记忆本来就是会遗忘的,关键是及时复习。我的做法是:每学完一个章节,就在日历上标记好复习时间(三天后、一周后、一个月后)。到了复习时间,就花一点时间回顾一下。这样虽然每次花的时间不多,但能有效地对抗遗忘。最后再分享一个小技巧:把你学到的算法讲给别人听。讲的过程就是梳理思路的过程,你会发现很多你以为懂了但其实没懂的地方。如果没有听众,就讲给自己听,或者写一篇博客。输出是最好的学习方式。
1. 为什么"算法基础课"这四个字值得反复刷很多人第一次听到"算法基础课"这五个字,第一反应是"这不就是给刚入门的人看的吗"。我一开始也这么想,直到自己刷完一遍、又回头刷第二遍、第三遍的时候,才发现这门课真正的价值根本不在"入门"两个字上,而在于它把一整套算法思维的骨架给你搭好了。我见过太多人学算法的路径是这样的:先找一本厚厚的算法书,翻两页看到数学公式就劝退;然后转去看各种零散的题解视频,东一榔头西一棒子,刷了两百道题还是遇到新题就懵。问题出在哪?出在没有体系。你脑子里装的是一道道孤立的题,而不是一类类问题的通用解法。算法基础课解决的就是这个问题。它把算法竞赛和面试里最核心的那批知识点——排序、二分、高精度、前缀和与差分、双指针、位运算、离散化、区间合并、链表、栈与队列、单调栈与单调队列、KMP、Trie、并查集、堆、哈希表、图论基础、最短路、最小生成树、二分图、数论、动态规划、贪心——按照从易到难、从基础到进阶的顺序串成一条线。你跟着这条线走一遍,脑子里就有了地图,知道每个知识点在哪个位置、和哪些知识点有关联、遇到什么类型的题该往哪个方向想。这篇文章不是给你复述课程目录的,那种东西你打开课程页面就能看到。我想聊的是:这门课到底该怎么刷才有效,哪些章节是必须反复啃的硬骨头,哪些地方新手最容易卡住,以及我自己在刷的过程中总结出来的一些"反常识"的经验。不管你是刚接触算法的新手,还是刷了几百道题想回头补基础的老手,下面这些内容应该都能帮你少走一些弯路。2. 刷课之前必须想清楚的三个问题2.1 你的目标决定了你的刷课策略同样是刷算法基础课,目标不同,刷法完全不一样。我把常见的目标分成三类,你可以对号入座:目标类型典型人群刷课重点建议投入时间打算法竞赛在校学生、竞赛选手全部章节精刷,重点在图论、数论、DP3-6个月,每天2-4小时准备技术面试求职者、转行人员排序、二分、双指针、哈希、DP、图论基础2-3个月,每天1-2小时补计算机基础工作多年的开发者按兴趣选章节,重点理解思想而非实现灵活,碎片时间即可我见过最典型的错误就是:一个准备面试的人,花了两周时间死磕KMP的next数组推导,结果面试根本没考字符串。不是说KMP不重要,而是投入产出比的问题。面试里最高频的是数组、字符串、哈希、双指针、二分、简单DP和二叉树相关的内容,这些才是你应该优先吃透的。反过来,如果你是想打竞赛,那就不能挑食。竞赛的题目往往是把多个知识点揉在一起考,你缺了任何一块都可能卡住。比如一道题可能同时用到离散化、前缀和和二分,你少会一个就做不出来。2.2 语言选择:别在这件事上纠结超过十分钟课程支持C++和Java两种语言。我的建议很直接:打竞赛,选C++。没别的原因,就是快。STL的vector、priority_queue、unordered_map这些容器在竞赛里用起来太顺手了,而且C++的常数小,同样的算法C++能过、Java可能就TLE(超时)。准备面试,选你未来工作要用的语言。面试官更关心你的思路,语言只是表达工具。如果你投的是Java岗位,那就用Java刷,顺便熟悉Java的集合框架。纯粹想学算法思想,选你写着最舒服的语言。思想是跨语言的,用Python写伪代码级别的实现也完全没问题。注意:不要因为"听说C++快"就硬着头皮用一门自己不熟的语言。语言不熟会严重拖慢你的刷题速度,而且会让你把注意力放在语法上而不是算法本身上。我见过有人用C++刷了一个月,还在为指针和引用的问题debug,这就本末倒置了。2.3 前置知识:你需要多少数学和编程基础这是很多人关心的问题。我的答案是:编程基础只需要会写循环、数组、函数就够了;数学基础只需要高中水平。课程里涉及到的数学知识主要是:质数判断、约数、欧拉函数、快速幂、扩展欧几里得、中国剩余定理、高斯消元、组合计数、容斥原理、博弈论。听起来很吓人对吧?但实际上每个知识点课程都会从最基础的定义讲起,你不需要提前学数论,跟着课程走就行。真正需要你提前具备的,是基本的代码调试能力。你得会看编译错误、会用打印大法找bug、能理解数组越界和整数溢出是怎么回事。这些能力不是看视频能学会的,只能通过自己动手写、自己踩坑来积累。3. 课程章节的难度分层与刷课节奏3.1 把二十多个章节分成四个梯队课程章节很多,如果平均用力,你会发现自己一直在舒适区里打转,或者一直在硬骨头面前卡死。我的做法是把章节按难度和重要性分成四个梯队,不同梯队用不同的策略。第一梯队:基础中的基础(必须滚瓜烂熟)排序、二分、高精度、前缀和与差分、双指针、位运算、离散化、区间合并。这些章节的特点是:代码短、思想直观、但应用极其广泛。排序和二分是后面几乎所有算法的基石,前缀和与差分是处理区间问题的万能工具,双指针是优化暴力的第一思路。这些章节的模板代码你必须做到闭着眼睛都能敲出来,不能有任何犹豫。我自己的标准是:第一梯队的每个模板,我都能在3分钟内从零写出来并且一次通过。如果你做不到,就反复练,练到形成肌肉记忆为止。第二梯队:数据结构入门(理解为主,实现为辅)链表、栈与队列、单调栈与单调队列、KMP、Trie、并查集、堆、哈希表。这些章节的核心是理解每种数据结构解决了什么问题。比如单调栈解决的是"找每个元素左边/右边第一个比它大/小的元素",并查集解决的是"动态连通性"问题,Trie解决的是"字符串前缀匹配"问题。实现细节当然也要掌握,但更重要的是看到一个问题能反应过来该用什么结构。这个反应速度是靠刷题量堆出来的,没有捷径。第三梯队:图论与数论(硬骨头,需要反复啃)图论基础、最短路、最小生成树、二分图、数论。这是整个课程里最硬的部分。图论的难点在于建模——给你一个实际问题,你要能抽象成图论模型,判断是最短路还是最小生成树还是二分图匹配。数论的难点在于推导——很多结论需要你自己推一遍才能真正理解,光背模板没用。我的建议是:这两个模块不要赶进度,一个章节可以花一周甚至两周。每道例题都要自己先想,想不出来再看题解,看完之后合上题解自己再写一遍。第四梯队:动态规划与贪心(思想性最强)动态规划、贪心。DP是公认的最难章节,没有之一。它的难不在于代码,而在于状态定义和转移方程的设计。同一个问题,状态定义不同,复杂度可能差一个数量级。贪心则难在证明——你怎么确定你的贪心策略是对的?这两个章节我没什么速成的办法,只能靠大量练习来培养感觉。但有一个技巧:做完每道DP题之后,问自己三个问题——状态表示的是什么?转移是怎么来的?边界是怎么处理的?把这三个问题想清楚了,这道题才算真正做完。3.2 刷课节奏:不要追求"刷完",要追求"刷透"我见过太多人把刷课当成看剧,一集接一集地看,看完一章就急着看下一章,结果看到DP的时候发现前面的全忘了。正确的节奏应该是:每学完一个章节,至少做10-20道对应的练习题,然后再进入下一章。练习题不用非得是课程配套的,你可以在各大刷题平台上按标签筛选。具体的时间分配,我建议是这样的:看视频/读讲义:占总时间的30%自己敲模板代码:占总时间的20%做练习题:占总时间的50%很多人把比例搞反了,花80%的时间看视频,20%的时间做题,结果就是"一看就会,一写就废"。提示:每学完一个章节,隔三天再回头默写一遍模板。如果还能默写出来,说明真的记住了;如果忘了,说明需要再练。这个"间隔重复"的方法比连续刷十遍有效得多。4. 那些让我卡了很久的硬骨头章节4.1 KMP:从"背模板"到"真理解"的转折点KMP是我第一个真正卡住的章节。当时我把模板背下来了,也能AC对应的模板题,但一到变形题就懵。后来我才意识到,我根本没理解next数组的含义。next数组的本质是:对于模式串的每个位置,记录它之前的最长相等前后缀的长度。这个"最长相等前后缀"就是KMP的核心。当你匹配失败的时候,你不需要从头开始,而是可以利用已经匹配的信息,把模式串往后滑动到下一个可能匹配的位置。理解了这个之后,很多变形题就迎刃而解了。比如"求字符串的最小循环节",其实就是用字符串长度减去next数组最后一个值;"求每个前缀出现的次数",就是沿着next数组往回跳。我的建议是:不要死记模板,自己画图推导一遍next数组的生成过程。拿一个具体的字符串,比如"ababab",手动算一遍它的next数组,然后再对照代码看。这个过程花不了半小时,但能让你真正理解KMP。4.2 动态规划:状态定义是唯一的门槛DP的难点只有一个:状态定义。一旦状态定义对了,转移方程往往是很自然的。我总结了一个定义状态的通用思路:问自己"我要决策什么,决策依赖什么信息"。比如背包问题,你要决策"第i个物品选不选",决策依赖于"当前还剩多少容量",所以状态就是"前i个物品,容量为j时的最优值"。再比如最长上升子序列,你要决策"第i个元素接在哪个元素后面",决策依赖于"前面以某个元素结尾的上升子序列的长度",所以状态就是"以第i个元素结尾的最长上升子序列长度"。这个思路能解决大部分DP问题的状态定义。当然,有些问题的状态定义比较巧妙,需要你做过类似的题才能想到。这就是为什么DP需要大量练习——你见过的状态定义越多,遇到新问题时能联想到的可能性就越大。4.3 图论建模:从"这题在说什么"到"这是个图论题"图论最难的从来不是算法本身,Dijkstra、SPFA、Kruskal这些算法的模板都很固定,难的是你怎么知道这道题要用图论做。我举几个典型的建模场景:最短路建模:题目里出现"从A到B的最少步数/最小花费/最短时间",而且状态之间有明确的转移关系,大概率是最短路。把每个状态看成图上的一个点,状态之间的转移看成边。最小生成树建模:题目里出现"用最小的代价把所有点连通",而且边是双向的、有权的,大概率是最小生成树。二分图建模:题目里出现"把一堆东西分成两组,组内有冲突"或者"匹配问题",大概率是二分图。这些建模能力不是看视频能学会的,只能通过做题来积累。我的建议是:每做完一道图论题,都问自己"这道题的关键建模步骤是什么",然后把这个建模思路记下来。积累多了,你看到新题就会有感觉。4.4 数论:推导比结论重要数论章节里有很多结论性的东西,比如欧拉定理、中国剩余定理、高斯消元。很多人只记结论,不记推导,结果遇到变形题就废了。我的建议是:每个结论都自己推一遍。比如欧拉定理,你要理解为什么a的φ(n)次方模n等于1,这个推导过程用到了剩余系的乘法封闭性。理解了推导,你才能知道这个定理的适用条件是什么、什么时候不能用。再比如扩展欧几里得,它的核心是裴蜀定理:ax+by=c有整数解当且仅当gcd(a,b)能整除c。理解了这一点,你才能知道为什么可以用扩展欧几里得求逆元、解同余方程。5. 模板代码的正确打开方式5.1 模板不是用来背的,是用来"长"在手上的很多人对模板代码有误解,觉得背下来就行了。但我的经验是:背下来的模板,一到关键时刻就会忘;只有自己推导过、写过的模板,才能真正长在手上。我的做法是这样的:每学一个算法,先理解它的思想,然后自己尝试写一遍,写不出来再看模板,看完之后合上模板再写一遍。这个过程可能要重复三四次,但每次重复你都会对算法有新的理解。比如快速排序,第一遍你可能只是照着模板敲;第二遍你会思考为什么基准值要随机选;第三遍你会理解为什么用do-while而不是while;第四遍你就能自己写出各种变体了。5.2 几个必须练到"肌肉记忆"的模板根据我的经验,下面这些模板是必须练到闭着眼睛都能写出来的:快速排序和归并排序。这两个是分治思想的代表,后面的很多算法都建立在它们之上。特别是归并排序,它的"合并两个有序数组"的思想在求逆序对、求区间和等问题里反复出现。整数二分和浮点二分。二分的边界处理是新手最容易出错的地方。我建议统一用一套自己习惯的写法,比如"左闭右开"或者"左闭右闭",然后所有二分题都用这套写法,不要每次换一种。前缀和与差分。一维和二维的都要会。前缀和用于快速求区间和,差分用于快速做区间修改。这两个是处理区间问题的万能工具。Dijkstra和SPFA。最短路的两大主力算法。Dijkstra用于非负权图,SPFA用于有负权边的图。两个都要会写堆优化版本。并查集。路径压缩和按秩合并都要会。并查集的代码很短,但应用极其广泛。KMP。虽然面试不一定考,但KMP的思想(利用已匹配信息避免重复计算)在很多地方都有体现。5.3 模板的常见坑即使是模板代码,也有一些容易踩的坑。我列几个最常见的:算法常见坑正确做法快速排序基准值选端点导致最坏O(n²)随机选基准值或选中点二分查找边界更新写错导致死循环统一用左闭右开或左闭右闭前缀和数组下标从0开始导致越界前缀和数组从1开始,a[0]=0Dijkstra忘记标记已访问导致重复入队出队时检查是否已访问并查集忘记路径压缩导致退化find时顺便压缩路径KMPnext数组下标含义搞混明确next[i]表示前i个字符的最长相等前后缀注意:这些坑我都踩过,而且不止一次。每次踩坑之后,我都会在代码注释里写清楚"这里为什么这么写",下次再写的时候就不会错了。6. 从"看懂"到"写对"的练习方法6.1 三遍刷题法我总结了一套"三遍刷题法",对我自己非常有效:第一遍:看题解,理解思路。不要一上来就自己硬想,那样效率太低。先看题解,理解这道题的核心思路是什么、用了什么算法、关键步骤是什么。第二遍:合上题解,自己写。看完题解之后,合上题解,自己从头写一遍。这一遍你可能会卡住,卡住的时候不要急着看题解,先自己想一想,实在想不出来再看。第三遍:隔几天再写一遍。隔三天到一周,再回头把这道题写一遍。如果还能写出来,说明你真的掌握了;如果忘了,说明需要再练。这个方法的核心是主动回忆。看题解是被动输入,自己写是主动输出,隔几天再写是间隔重复。三者结合,记忆效果最好。6.2 错题本的正确用法我强烈建议你准备一个错题本,但不要只是抄题目和答案。我的错题本里记录的是这些东西:这道题我为什么错了:是思路错了,还是边界处理错了,还是模板写错了?正确的思路是什么:用一两句话概括核心思路。这道题的关键步骤是什么:哪一步是我想不到的?有没有类似的题:这道题和之前做过的哪道题类似?这样记录的好处是:复习的时候你不是在重新做一遍题,而是在回顾自己的思维盲区。这比盲目刷题有效得多。6.3 不要害怕看题解很多人觉得看题解是"作弊",非要自己硬想。我的观点是:看题解不丢人,丢人的是看完题解就忘了。算法学习本来就是一个"模仿-理解-创造"的过程。你先看别人怎么做的,理解别人的思路,然后自己模仿着做,最后才能自己创造新的解法。没有前面的模仿和理解,你不可能凭空创造出解法。关键是看完题解之后要自己写一遍,而且要隔几天再写一遍。只要做到这两点,看题解就是高效的学习方式。7. 刷完基础课之后的路怎么走7.1 从模板题到综合题的过渡刷完基础课之后,你会发现一个尴尬的现象:模板题都会做,但一遇到综合题就懵。这是因为基础课的题目大多是单一知识点的,而综合题往往需要多个知识点组合。过渡的方法很简单:开始刷综合题,从简单的开始。比如一道题可能同时用到前缀和和二分,你先分别想清楚每个部分怎么做,再把它们组合起来。一开始可能会很慢,但做多了就有感觉了。我建议的过渡路径是:先刷各平台的"简单"难度的综合题,然后逐渐过渡到"中等",最后再挑战"困难"。不要一上来就刷难题,那样只会打击信心。7.2 进阶方向的选择刷完基础课之后,你可以根据自己的目标选择进阶方向:打竞赛:继续学进阶算法,比如线段树、树状数组、AC自动机、后缀数组、网络流、计算几何等。准备面试:开始刷面试高频题,重点练习数组、字符串、链表、二叉树、DP、设计题。提升工程能力:把学到的算法应用到实际项目中,比如用图论做路径规划、用DP做资源分配、用并查集做连通性分析。不管选哪个方向,基础课打下的底子都是有用的。算法思想是通用的,你在这里学到的分治、贪心、DP、图论建模,在任何一个方向都用得上。7.3 保持手感:定期回顾算法这东西,不用就会忘。我自己的做法是:每周至少做两道题保持手感,每个月回头复习一个章节的模板。复习的时候不用重新做所有题,只需要默写模板、做一两道典型题就够了。关键是不要让手生。提示:如果你已经工作了,时间不多,可以只保持每周一道题的节奏。但一定要坚持,一旦断了超过一个月,再捡起来就很痛苦了。8. 我踩过的那些坑和总结的经验8.1 不要追求刷题数量我刚开始刷题的时候,特别在意"刷了多少道"。看到别人刷了500道,我就想刷600道。结果就是:刷过的题过两周就忘了,遇到新题还是不会。后来我改变了策略:不再追求数量,而是追求"每道题都真正搞懂"。一道题做三遍,比三道题各做一遍效果好得多。刷题数量从500降到200,但我的实际水平反而提高了。8.2 不要跳过"简单"的章节很多人觉得排序、二分、前缀和这些太简单了,随便看看就跳过了。但我的经验是:这些"简单"章节恰恰是最重要的。排序和二分是后面几乎所有算法的基石,前缀和与差分是处理区间问题的万能工具。这些章节的模板你必须练到滚瓜烂熟,不能有任何含糊。我见过太多人因为二分边界写错而WA(答案错误),因为前缀和下标搞混而RE(运行时错误)。8.3 不要一个人死磕遇到卡住的地方,不要一个人死磕超过半小时。超过半小时还没思路,就去看题解或者问别人。死磕的代价是巨大的:你会浪费大量时间,而且容易产生挫败感。当然,看题解之前要先确保自己真的理解了题目,知道自己卡在哪一步。带着问题去看题解,效果比盲目看要好得多。8.4 写代码之前先写伪代码这是我后来养成的一个习惯:写代码之前,先用中文把思路写一遍。比如:1. 读入n和数组a 2. 对a进行排序 3. 用双指针i=0, j=n-1 4. while i < j: 5. 如果a[i]+a[j] == target,返回 6. 如果a[i]+a[j] < target,i++ 7. 否则j--这个伪代码写出来之后,再翻译成实际代码,出错的概率会大大降低。而且如果思路有问题,在伪代码阶段就能发现,不用等到debug的时候才发现。8.5 重视边界情况算法题最容易出错的地方就是边界情况。我总结了几种常见的边界:空输入:n=0的情况单元素:n=1的情况全部相同:所有元素都相等的情况极值:最大值、最小值、负数的情况溢出:中间结果超过int范围的情况每次写完代码,我都会用这几组边界数据测一遍。这个习惯帮我避免了很多WA。8.6 学会看别人的代码刷题平台上每道题都有很多人的提交记录,我建议你做完一道题之后,去看看排名靠前的代码。你会发现很多巧妙的写法,比如用位运算代替除法、用数组代替哈希表、用递归代替循环。看别人的代码不是为了抄,而是为了拓宽思路。你可能会发现,原来这个问题还可以这样解。这些思路积累多了,你自己写代码的时候就会有更多的选择。9. 给不同阶段学习者的具体建议9.1 零基础新手:先跑通,再理解如果你完全没接触过算法,我的建议是:先跑通,再理解。不要一上来就试图理解每个算法的证明和推导,那样你会被劝退。先跟着课程把代码敲一遍,让它跑起来,看到正确的结果。然后慢慢去理解为什么这样写是对的。比如快速排序,你先照着模板敲一遍,看到它能正确排序,然后再去理解分治的思想、基准值的作用、递归的过程。这个顺序比反过来要容易得多。9.2 有基础但不成体系:按章节查漏补缺如果你已经刷了一些题,但感觉知识点很散,我的建议是:按章节查漏补缺。先快速过一遍课程目录,看看哪些章节你是完全不会的,哪些是会用但不熟的,哪些是已经掌握的。然后重点攻克"会用但不熟"的章节,这些章节往往是你的薄弱环节。对于"完全不会"的章节,不要急着学,先确保前面的基础章节都掌握了。算法学习是有依赖关系的,前面的没学好,后面的很难理解。9.3 准备面试:重点突破高频考点如果你是为了准备面试,我的建议是:重点突破高频考点。根据我的经验,面试里最高频的算法考点是:数组和字符串操作、双指针、二分查找、哈希表、链表、二叉树遍历、简单DP、BFS/DFS。这些内容在基础课里都有覆盖,你需要做的是针对性地多练。至于KMP、数论、复杂图论这些,面试里出现的概率很低,可以暂时放一放。等你把高频考点都吃透了,再回头补这些也不迟。9.4 工作多年想补基础:带着问题学如果你已经工作多年,想补一下算法基础,我的建议是:带着问题学。不要像学生时代那样从头到尾刷一遍,那样太耗时了。你可以先想想自己工作中遇到过哪些和算法相关的问题,比如性能优化、数据处理、路径规划等,然后针对性地去学相关的章节。这样学的好处是:你有实际的应用场景,理解起来会更快,而且学完就能用上,记忆也更深刻。10. 一些零散但有用的心得10.1 关于学习时间算法学习最忌讳的就是"三天打鱼两天晒网"。每天学一小时,坚持三个月,比周末突击十小时效果好得多。因为算法学习需要持续的思维训练,断断续续的话,每次都要重新进入状态。我自己的习惯是:每天早上花一小时刷题,雷打不动。这一小时里,我不看手机、不刷消息,就专注做题。这一小时的效果,比我晚上断断续续学三小时还要好。10.2 关于学习环境找一个安静的环境,关掉所有通知。算法学习需要深度思考,任何打断都会让你重新进入状态。我一般会把手机调成静音,放在另一个房间。另外,准备一个顺手的IDE或者编辑器。我见过有人用记事本写代码,然后抱怨效率低。工具不顺手,会严重影响你的学习体验。10.3 关于心态算法学习是一个长期积累的过程,不要指望速成。你可能会遇到这样的情况:刷了一个月,感觉没什么进步;或者做了一道题,怎么都想不出来。这些都是正常的。我自己的经验是:进步往往不是线性的,而是阶梯式的。你可能连续几周都感觉在原地踏步,然后突然有一天,你发现自己能做出之前做不出的题了。这就是量变到质变的过程。所以,不要因为一时的挫败就放弃。坚持下去,你会看到自己的进步的。10.4 关于复习复习比学习更重要。我自己的复习节奏是这样的:每天:花10分钟默写一个模板每周:花一小时复习一个章节的典型题每月:花半天时间,把当月学的内容整体过一遍这个复习节奏看起来花了不少时间,但实际上它帮你省下了更多的时间——因为你不用反复重新学习已经忘掉的东西。10.5 关于刷题平台刷题平台不用贪多,选一个主流的、题目质量高的就行。重要的是把题目做透,而不是在多个平台之间来回切换。我自己的做法是:主刷一个平台,遇到特别好的题或者特别典型的题,再在其他平台找类似的题做对比。这样既能保证题目的质量,又能拓宽视野。10.6 关于代码风格从一开始就养成好的代码风格。变量命名要有意义,不要用a、b、c这种;关键步骤要写注释;代码结构要清晰,不要把所有逻辑都塞在一个函数里。好的代码风格不仅让代码更容易读懂,也让你在debug的时候更容易定位问题。我见过很多人因为变量命名混乱,debug的时候自己都看不懂自己写的代码。10.7 关于"刷不动"的时候每个人都会有"刷不动"的时候。这时候不要硬撑,可以换个方式:看看算法相关的科普视频,换个角度理解做一道简单的题找找信心出去走走,让大脑放松一下和同学/同事讨论一下,说不定会有新的思路硬撑只会让你越来越烦躁,效率越来越低。适当放松,反而能让你更快地恢复状态。10.8 关于"看懂了但写不出"这是最常见的问题。看题解的时候觉得"哦,原来这么简单",但自己写的时候就是写不出来。原因很简单:你只是看懂了,但没有真正理解。解决方法是:看完题解之后,合上题解,自己从头写一遍。写不出来就再看,看完再写,直到能独立写出来为止。这个过程可能很痛苦,但这是唯一有效的方法。10.9 关于"一题多解"很多题都有多种解法。比如最短路问题,可以用Dijkstra、Bellman-Ford、SPFA、Floyd等多种算法。我的建议是:至少掌握两种解法。掌握多种解法的好处是:你可以根据题目的特点选择最合适的算法。比如数据范围小的时候可以用Floyd,数据范围大的时候用Dijkstra;没有负权边的时候用Dijkstra,有负权边的时候用SPFA。10.10 关于"学完就忘"这是正常的,不用焦虑。人的记忆本来就是会遗忘的,关键是及时复习。我的做法是:每学完一个章节,就在日历上标记好复习时间(三天后、一周后、一个月后)。到了复习时间,就花一点时间回顾一下。这样虽然每次花的时间不多,但能有效地对抗遗忘。最后再分享一个小技巧:把你学到的算法讲给别人听。讲的过程就是梳理思路的过程,你会发现很多你以为懂了但其实没懂的地方。如果没有听众,就讲给自己听,或者写一篇博客。输出是最好的学习方式。