☰
算法设计与分析48小时考前突击:复杂度、DP填表与手写模板
2026/10/7 7:30:31 网站建设 项目流程

算法设计与分析这门课,在计算机专业的培养方案里属于那种"平时不听课、期末火葬场"的硬骨头。它跟数据结构不一样,数据结构还能靠刷题练手感,这门课要求你在两小时内既能把一个递推式的时间复杂度算得清清楚楚,又能现场手推矩阵连乘的填表过程,还得在草稿纸上默写出完整的归并排序或者KMP的next数组。所以考前突击这件事,对算法设计与分析来说,策略远比蛮力重要。我把自己和身边同学在48小时内从几乎零基础冲到及格线以上的整套打法梳理了出来,覆盖复杂度分析、分治、动态规划、贪心、图算法、回溯与分支限界、NP完全性这几大板块,同时把高频编程题的手写模板和临场取舍技巧一起写清楚。无论你是前期完全没听、现在只剩两个通宵的极限选手,还是想临门补漏多抢十几分的稳过党,下面这些内容都能直接拿来用。

1. 考前突击的底层逻辑:先搞明白这门课到底考什么

很多人一上来就抱着教材从第一章啃,结果第一章的数学预备知识就劝退了。突击的第一原则是:别按教材顺序复习,按考试分值分布复习。把有限的十几个小时砸在分值最高、最容易拿分的模块上,才叫突击。

1.1 从题型分布倒推复习优先级

算法设计与分析这门课的期末卷子,绝大多数学校的结构都很稳定,基本逃不出下面这几类题。我把它们按"提分效率"排了个序,也就是单位复习时间能换回多少分。

题型大致分值占比复习性价比说明
复杂度计算与递推式求解15%~25%极高公式固定,练几道就能上手
算法过程手推(填表类)20%~30%高动态规划、矩阵连乘、最短路填表
简答与概念辨析10%~15%中P/NP/NPC、贪心与DP区别
算法设计与伪代码20%~30%中现场设计+手写代码
证明题10%~15%低正确性证明、归约证明,临场难速成

从这个表能看出来,复杂度计算和过程手推两块加起来往往就占了一半分。这两块的特点是:套路化、可训练、有标准答案。你花两个小时把主定理和常见递推式练熟,可能比啃一晚上NP完全性拿的分还多。所以突击的路线很明确——先保复杂度计算,再抠DP填表,最后才碰证明题。

有同学会问,那编程题和设计题怎么办?我的建议是把它们当成"半背诵半理解"来处理。考试里让你从零设计一个全新算法的概率其实不高,更多是让你在经典算法上改一改,比如"在0-1背包基础上加一个约束"。你把经典模板背熟,改起来就有底气。

1.2 突击和系统学习的区别在哪

系统学习是从定义出发,理解每个算法为什么被发明出来、解决了什么问题、正确性怎么证明。突击完全相反,它是从"考场上要写什么"倒推回来。举个例子,系统学习快速排序,你要理解分治思想、划分策略、平均复杂度推导;而突击只需要你知道:划分函数怎么写、最好最坏平均复杂度分别是多少、什么情况下退化成O(n²)。

这不是说突击可以不求甚解,而是说突击要把理解压缩到"够用"的程度。我的经验是,突击时对每个算法问自己三个问题就够了:它解决什么问题、它的核心步骤是什么、它的复杂度是多少。这三个问题能答上来,简答和计算题基本就稳了。真正需要深挖的只有一类——你算不准复杂度的算法,那就必须把递推式老老实实推一遍。

提示:突击阶段千万不要试图把每个算法的正确性证明都看懂,时间根本不够。证明题临场尽量写,但别为它牺牲计算题的练习时间。

2. 复杂度分析:所有题目的地基,必须先啃下来

复杂度分析是这门课的地基,也是突击阶段最值得先投入的地方。因为它几乎渗透在每一道题里——你写个伪代码老师要你标复杂度,你做DP填表要分析时间复杂度,你判断P和NP的关系也绕不开多项式时间。这块啃不下来,后面全是空中楼阁。

2.1 渐进符号和递推式求解

渐进符号这一块,考试基本就考三个:O、Ω、Θ。定义要能说清楚,但更重要的是会算。常见函数的增长速度排序必须烂熟于心,考场上经常让你排序或者判断谁是谁的上界。记住这个顺序:常数量级 < 对数 < 多项式 < 指数 < 阶乘,具体展开就是 1 < log n < √n < n < n log n < n² < n³ < 2ⁿ < n!。

递推式求解是重点中的重点。突击阶段你至少要熟练三种方法:代入法、递归树法、主定理。

代入法适合验证答案,递归树法适合直观估算,主定理适合快速出结果。我重点说主定理,因为它是考场上最省时间的方法。主定理处理的是形如 T(n) = aT(n/b) + f(n) 的递推式,其中 a≥1、b>1。核心思路是比较 f(n) 和 n^(log_b a) 这两个量的大小关系:

  • 第一种情况,如果 n^(log_b a) 比 f(n) 大一个多项式量级,也就是 f(n) = O(n^(log_b a - ε)),那么 T(n) = Θ(n^(log_b a))。
  • 第二种情况,如果两者同阶,即 f(n) = Θ(n^(log_b a)),那么 T(n) = Θ(n^(log_b a) · log n)。
  • 第三种情况,如果 f(n) 更大且满足正则条件 a·f(n/b) ≤ c·f(n),那么 T(n) = Θ(f(n))。

光看定义容易晕,我给个实战例子。归并排序的递推式是 T(n) = 2T(n/2) + Θ(n)。这里 a=2,b=2,n^(log_2 2) = n,而 f(n) = Θ(n),两者同阶,命中第二种情况,所以 T(n) = Θ(n log n)。再看二分查找 T(n) = T(n/2) + Θ(1),a=1,b=2,n^(log_2 1) = n⁰ = 1,f(n) = Θ(1),同阶,命中第二种,T(n) = Θ(log n)。这两个是最经典的考法,必须做到看题就能报答案。

注意:主定理不是万能的,遇到 f(n) 落在三种情况缝隙里的递推式,比如 T(n) = 2T(n/2) + n log n,主定理失效,这时候只能老老实实用递归树或代入法。考试里这种"故意不让你用主定理"的题偶尔出现,看到 a 和 b 配出来的结果和 f(n) 差一个 log 因子,就要警觉。

2.2 递归树法的手推技巧

递归树法是突击阶段必须掌握的第二把武器,因为它既能算复杂度,又能直观解释复杂度是怎么来的,简答题里写出来很加分。以 T(n) = 2T(n/2) + n 为例,画递归树的过程是这样的:第一层代价是 n,第二层分成两个子问题、每个代价 n/2,加起来还是 n,第三层四个子问题、每个代价 n/4,加起来仍然是 n。每一层的代价都是 n,一共分了 log₂n 层,最后一层叶子代价是 Θ(n) 个常数,于是总代价是 n·log n + n = Θ(n log n)。

这个"每层都是 n、一共 log n 层"的直觉一定要建立起来,考试时如果时间紧,直接画两层说明每层代价相等、层数是对数级,就能拿大部分过程分。递归树的另一个好处是能处理主定理覆盖不到的情况。比如 T(n) = T(n/3) + T(2n/3) + n,这不是标准的主定理形式,但画树会发现每一层代价都是 n,而最短路径是沿着 n/3 走到 1,深度是 log₃n,最长路径沿着 2n/3 走,深度是 log_{3/2}n,两边只差常数倍,所以总复杂度还是 Θ(n log n)。

3. 六大算法范式:抓住每个范式的"命门"

教材里通常会把算法分成若干设计范式:分治、动态规划、贪心、回溯、分支限界,再加上图算法里那些专门的算法。突击时不要平均用力,每个范式只需要抓住它最典型的例题和最容易混的点。

3.1 分治法与动态规划的区别和联系

这两个是考试里最爱考、也最容易混的一对。核心区别在于子问题是否重叠。分治法的子问题是相互独立的,各管各的,解完再合并;动态规划的子问题有重叠,同一个子问题会被反复用到,所以要用表格把中间结果存下来,避免重复计算。

我常用的一个类比是:分治法像一个大项目拆成几个互不相干的小组,各组独立干活,最后项目经理把成果拼起来;动态规划像一道数学题里有很多小台阶,你踩过一次的台阶要记下来,不然每次都重新算就慢死了。

分治法的高频例题就那几个:归并排序、快速排序、二分查找、最大子数组、最近点对、大整数乘法。突击时每个都要能默写出伪代码。动态规划的高频例题更集中:矩阵连乘、最长公共子序列(LCS)、0-1背包、最长递增子序列、编辑距离、最优二叉搜索树。这几道题在期末卷里出现的频率高得惊人,几乎是必考。

3.2 动态规划填表的手推流程

动态规划的手推过程是期末卷的送分题也是丢分题,因为步骤多、容易算错。我用矩阵连乘和0-1背包这两道最典型的题,把流程拆开讲。

矩阵连乘的核心是填写二维表 m[i][j],表示从第 i 个矩阵乘到第 j 个矩阵所需的最少乘法次数。递推式是:

m[i][j] = min{ m[i][k] + m[k+1][j] + p_{i-1}·p_k·p_j },其中 k 从 i 取到 j-1。

手推时的正确顺序是按链长从小到大填,先填长度为2的对角线,再填长度3,一直填到长度 n。为什么必须按链长填?因为长链依赖短链的结果,短链没算出来,长链的 min 就无从谈起。这个"按子问题规模从小到大填表"的思想,是所有DP手推题的通用逻辑。

0-1背包的核心表是 v[i][j],表示前 i 个物品、容量为 j 时的最大价值。递推式是:

  • 如果第 i 个物品的重量 w_i > j,那么 v[i][j] = v[i-1][j];
  • 否则 v[i][j] = max(v[i-1][j], v[i-1][j-w_i] + v_i)。

手推时要注意容量维度通常从0填到背包总容量,物品维度从上往下填,每个格子取值只看它上方和左上方对应位置的结果。我踩过的一个坑是:很多同学把 v[i-1][j-w_i] 记成 v[i][j-w_i],结果算出来的答案偏大,因为前者保证了物品不重复放,后者则允许重复放,就变成了完全背包。这个细节一定要在考场上盯住。

3.3 贪心、回溯、分支限界各自守哪块阵地

贪心的命门是"局部最优能不能推出全局最优",也就是贪心选择性质和最优子结构。考场上判断一个贪心策略对不对,最有效的办法是举反例。经典的贪心例题有活动安排、哈夫曼编码、最小生成树(Prim 和 Kruskal)、单源最短路(Dijkstra)、部分背包。要注意的是,0-1背包不能用贪心,因为按单位价值排序后装不满时的取舍会导致整体次优解,这个反例一定要会举。

回溯法本质上是有剪枝的深度优先搜索,核心考点是解空间树的画法、剪枝条件的判断,以及N皇后、子集和、图着色、旅行商这些经典题。考试让你画解空间树时,务必标清楚哪些节点被剪掉了以及为什么,这些剪枝理由往往就是得分点。

分支限界和回溯的区别在于搜索策略:回溯是深度优先,分支限界通常用广度优先或优先队列,并维护一个限界函数来控制搜索范围。突击阶段掌握0-1背包的分支限界和旅行商的分支限界就够了,重点是理解限界函数怎么算,比如用贪心解作为上界来剪枝。

4. 图算法与NP理论:概念辨析别丢分

图算法和NP理论这两块,计算题和概念题混杂,突击时要分清哪些是需要手推的、哪些是纯背的。

4.1 最短路与最小生成树的模板对比

这块最容易混的是:哪些算法处理带负权边、哪些用邻接矩阵、哪些用优先队列。我给一个速查表,考场上直接对号入座。

算法解决问题数据结构能否负权复杂度
Dijkstra单源最短路优先队列否O((V+E)log V)
Bellman-Ford单源最短路边集能O(VE)
Floyd多源最短路邻接矩阵能O(V³)
Prim最小生成树优先队列无向图O(E log V)
Kruskal最小生成树并查集无向图O(E log E)

Dijkstra 为什么不能处理负权边?因为它基于贪心,一旦一个顶点被标记为"已确定最短路径"就不再更新,而负权边的存在可能导致后面出现更短的路径,破坏了这个前提。这个原因经常出现在简答题里。Floyd 的三重循环顺序也是考点:k 那一层必须放最外层,因为它代表"允许经过的中间顶点集合",如果放错层,结果就错了。

4.2 NP完全性:背熟定义和经典归约

NP理论这块,突击就别想着深入理解了,把定义和几个经典结论背熟最划算。必背的核心概念有:

  • P类问题:能在多项式时间内解决的问题。
  • NP类问题:能在多项式时间内验证一个解是否正确的问题。
  • NP完全问题(NPC):既是NP问题,又是NP难问题。只要任何一个NPC问题能在多项式时间内解决,那么所有NP问题都能。
  • NP难问题(NP-hard):至少和NPC问题一样难,但不一定属于NP。

经典的NPC问题必须能报出来几个:SAT(布尔可满足性)、3-SAT、团问题、顶点覆盖、哈密顿回路、旅行商问题、子集和问题。归约这块,考试大概率只考"从哪个问题归约到哪个问题",你记住几条经典归约链就够了,比如3-SAT归约到团问题、团问题归约到顶点覆盖、3-SAT归约到子集和。归约的方向千万别搞反:是把已知的NPC问题归约到待证明的问题上,才能证明待证明的问题也是NPC。

5. 编程题突击:手写代码的临场生存法则

算法设计与分析的编程题通常是手写伪代码或C/C++代码,考察的是你能不能把脑子里的思路准确地落在纸上。这块的突击重点是模板化,把高频算法写成自己顺手的固定格式,考场上直接套。

5.1 高频手写代码清单

突击阶段最值得默写的几个代码模板,按考频排:

  1. 归并排序和快速排序的分治框架,包括 partition 函数。
  2. 二分查找的两种写法(闭区间和左闭右开)。
  3. 0-1背包的一维滚动数组优化写法。
  4. LCS 的二维DP填表。
  5. Dijkstra 的优先队列版。
  6. KMP 的 next 数组构造。
  7. 二叉树的前中后序非递归遍历。

我建议把每个模板手抄三遍,抄的时候不要看答案,抄完对照检查。抄写比看更有效,因为手写代码这件事本身就是肌肉记忆,考场上你的手比脑子更快。一维滚动数组的0-1背包尤其值得练,因为它的内层循环必须逆序,这个细节特别容易在紧张时写反:

for (int i = 0; i < n; i++) for (int j = W; j >= w[i]; j--) // 逆序!保证物品只用一次 dp[j] = max(dp[j], dp[j - w[i]] + v[i]);

如果这里写成正序,就变成完全背包了,这也是老师爱设的陷阱。

5.2 手写代码的排版与验证技巧

考场上写代码,卷面质量和逻辑正确同样重要。我的经验是三点:第一,先写函数签名和参数说明,让老师一眼看出你理解题意;第二,代码里关键步骤写注释,尤其是循环变量的含义和边界含义,老师看注释就知道你懂不懂;第三,写完自己用一个小例子走一遍,我最常用的是 n=3 或者数组长度等于3的小例子,几步就能验证边界。

实操心得:手推小例子时,专门检查三种边界——空输入、单元素、恰好等于某个临界值的输入。很多同学代码逻辑没错,就是边界处理写错,比如循环写成 i < n-1 导致最后一个元素没处理,白丢分。

6. 考场应急与常见翻车现场

突击的最后一步,是把考场上最容易犯的错提前排掉。我把平时和考后复盘时发现的高频问题整理成一张速查表。

翻车现场典型表现应急处理
复杂度算错递推式套错主定理情况用 n=8 代进去数一数操作次数
DP填表方向错长链先填、依赖短链按子问题规模从小到大重填
贪心策略错用了不满足贪心选择性质的策略快速举一个反例自检
归约方向反把待证问题归约到已知问题记住"已知→待证"才有效
代码边界错循环少跑一次或多跑一次用长度3的小例子手动跑

关于复杂度算错,我再补一个自查方法:如果你算出来是 O(n log n),就代 n=8,log 层数是3,每层大概是8次操作,总数约24次,量级对得上;如果你算出来是 O(n),但代进去发现层数明显是 log 级,那肯定错了。这种"代数字验证数量级"的习惯,能救回不少计算题。

时间不够时的取舍也是学问。我的策略是:先扫一遍全卷,把会做的题标记出来,优先做计算题和填表题,这两类分多且稳;证明题和设计题放在最后,能写多少写多少,公式和思路写上去通常都有过程分,千万别空着。有一次我留了一道证明题没写,考后对答案发现论证的关键一步其实我会,白白丢了六分,从那以后我宁可写半页废话也不留空白——当然,这里的"废话"指的是相关的公式和思路,不是瞎写。

很多同学问这门课到底能不能两天突击过。我的回答是:及格线附近完全可以,想拿高分就得靠平时。但如果你只是想过,那上面这套"重计算、轻证明、模板化编程"的打法,配合真题练习,两天时间足够把及格线摸到。真正决定成败的其实不是聪明程度,而是你有没有在最后48小时里把有限的注意力精准地砸在分值最高的模块上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询