☰
算法期末综合大题攻略:动态规划、贪心与图论建模技巧
2026/10/7 1:26:43 网站建设 项目流程

考场上最安静的时候,就是老师把《算法分析与设计》期末卷子翻到最后一页的时刻。我听到身边几个同学同时吸了一口气——第五道大题,工工整整地占了半页纸,分值栏里写着20分。前面四道题再怎么折腾,考的都是熟练度;这一道,考的是你能不能把一整门课的东西串起来,在有限时间里设计出一个又对又快的算法。我当年在这道题上吃过亏,复习时也走过弯路,所以把这道题的出题规律、答题思路和踩坑经验整理成了一份汇总,给准备这门课的学弟学妹一个参考。无论你目前是刚学完分治和动态规划、对图论还有点懵,还是已经进入刷题冲刺阶段,这份东西都能帮你把最后一道大题从"碰运气"变成"稳定拿分"。

这份汇总的来路我先说明白:我结合了课程平时作业、历年期末的大题方向、以及我身边高分同学复盘出来的经验,按题型整理出几个高频考点。算法设计与分析这门课,核心就那几块——分治、动态规划、贪心、回溯、图论算法。第五道大题不会考那种"背模板就能写出来"的题,它一定会做一点变形,往某个实际场景上靠一靠。你要做的不是记题,而是建立一套"看到题就能判断题型、套出框架"的应激反应。

1. 第五道大题到底在考什么:从试卷结构看它的真正身份

1.1 为什么这道题能直接决定你拿的是优秀还是及格

先说说第五道大题在整张卷子里的地位。吉大算法设计与分析的期末试卷,题型分布通常比较固定:选择填空考概念,前面几道大题考某一类具体算法(比如让你手写快排过程、计算某个动态规划的状态转移表、给出Dijkstra算法的执行步骤),最后一道大题则是综合设计题。这个位置不是随便放的——它考察的是你能否把学过的算法思想应用到"没做过的新题目"上。

这也就解释了为什么许多同学平时作业都能完成,但一到第五道大题就卡壳。因为前面的题是"知识的复现",最后一道题是"知识的迁移"。它会给你一个看起来不太像课本习题的场景,比如"某个仓库需要安排货物配送""某段DNA序列需要做相似度比对""某个教务系统需要排出不冲突的考试时间表"。你需要自己判断该用哪种算法,自己设计数据结构,自己写伪代码,还要分析时间复杂度和空间复杂度。换句话说,前面四道题是证明你上课听讲了,这道题是证明你真的会思考了。

1.2 一道题里隐含的三层能力要求

第一层是建模能力。场景描述往往是一大段文字,你得把它翻译成一个数学问题或图论问题。比如"仓库配送"本质是最短路径,"考试时间表"本质是图着色或区间调度,"DNA比对"本质是字符串编辑距离。这一步做不对,后面全白搭。

第二层是算法设计能力。判断出题型之后,你要在几分钟内选定一个核心算法策略,并且根据题目给你的数据范围决定具体实现方式。数据量小,可能直接暴力搜索就够了;数据量中等,考虑贪心或动态规划;数据量大,就得想想堆优化、滚动数组这类进阶手段。

第三层是表达与证明能力。卷子上作答不是写代码跑通就行,你要用伪代码把算法讲清楚,还要说明为什么这个算法是对的、复杂度是多少。我见过不少同学算法思路完全正确,但因为伪代码写得一团乱、复杂度分析漏掉某个循环,被扣了很多冤枉分。

1.3 一个很多人没意识到的事情:这道题其实有规律可循

我在复习后期最大的顿悟就是:不要被第五道大题的"综合设计"吓住,它的考点范围其实比前面几道题更窄。因为出题老师要保证一道题能在20分钟内写完、能在官方答案里给出一套说得通的建模,所以它翻来覆去就是那几个经典模型的生活化变体。

为了让这个判断不变成我的一家之言,我特意翻了近几年的题型回忆,也和几个不同年级的同学对过说法,基本可以确认:动态规划类题目出现频率最高,贪心算法和图论算法紧随其后,回溯和分治偶尔出现但大多是作为解题过程的一部分。这个分布其实也合理——动态规划最能体现算法设计的巧思,答案也不唯一,给分点容易设置;贪心算法的难点在于证明;图论算法的难点在于建模。接下来我按这几个方向展开说,每类都会给出识别特征、解题框架和一个完整的模拟例题。

2. 动态规划大题:最常出现,也是最容易丢分的题型

2.1 怎么一眼看出这道题要用动态规划

期末卷子上的DP题不会直接告诉你"请使用动态规划",但题干里几乎都有明显的信号词。我把它总结成三句话:

  • 题目要求的是"最值":最大收益、最少代价、最长子序列、最短编辑距离;
  • 问题的决策可以拆成多个阶段,每个阶段的选择会影响后面的结果;
  • 直接暴力枚举的复杂度高到不可接受,通常是指数级的,而输入规模偏偏很大。

举个例子,我当年遇到的一道模拟题是这么描述的:给定两个字符串A和B,允许进行插入、删除、替换三种操作,问最少需要多少次操作能把A变成B。如果你有刷题经验,一眼就知道这是编辑距离问题。如果你没见过这道题也没关系,从信号词入手:求"最少操作次数"是最值问题,每次对字符的操作是一个阶段决策,暴力枚举所有操作序列的复杂度是指数级的——三条全中,大概率就是DP。

2.2 DP题的核心功夫:状态定义和转移方程

很多人卡在DP,不是因为不懂原理,而是不知道状态怎么定。我自己的经验是,大部分期末DP题的套路其实都藏在那几本经典教材里——背包、最长公共子序列、最长递增子序列、编辑距离、矩阵链乘、区间DP。你把课本上这几种模型的状态定义吃透,考试时稍微变一变就能套上。

以一个我们复习时反复练的变体为例子说明。题目是:有一个环形数组,首尾相接,求最大连续子数组和。如果不知道环形这个坑,直接按照线性最大子段和的思路做,那就是O(n)扫描加贪心,但对环形情况是错的。正确的处理方式是分两种情况讨论:答案要么来自数组中间某一段连续子数组,要么来自首尾相连的一部分。后者可以通过"总和减去中间某段最小子段和"来得到。这两种结果取较大值就是答案。

这个题型的DP状态定义就很经典:设dp[i]表示以第i个元素结尾的最大子段和,转移方程是dp[i] = max(dp[i-1] + a[i], a[i])。边界是dp[1] = a[1]。然后在扫描过程中维护一个全局最大值。如果考试时能把这两种情况都考虑到,并写清楚注释说明为什么这样分情况,阅卷老师想扣分都找不到理由。

2.3 边界条件和初始化,期末考场上最容易翻车的地方

DP题得分的关键,一半在状态转移方程,另一半在边界条件。我批过几份学弟学妹的模拟卷(我们复习小组互相批改),发现最常见的扣分点就是:

  • 初始化时把所有dp数组元素设成0,但对某些题目来说这个初始值是不对的。比如编辑距离问题,dp[0][j] = j,dp[i][0] = i,这个表示空串变到另一个串需要全部插入或删除。漏掉这行初始化,整个表全是错的。
  • 递推方向搞反。有的DP依赖于前面的状态,就必须从前向后推;有的依赖于后面的状态,就得从后向前推。考试时最稳妥的做法是把状态转移方程先写出来,明确dp[i]依赖哪些下标,再决定循环方向。
  • 空间优化的时候丢失信息。滚动数组是个好技巧,但如果你压缩了空间却把用于回溯路径的信息丢了,遇到"不仅要求最优值还要求具体方案"的题就会吃亏。期末题偶尔会出"请给出具体操作序列"这种问法,考场上如果遇到,优先写完整二维版本,不要为省那点空间冒丢分的风险。

2.4 一个完整的DP实战演示:编辑距离及其背包变体

我用编辑距离这道经典题给大家完整走一遍考场作答的全过程。题目描述通常长这样:给定两个字符串word1和word2,你可以对一个字符串执行插入、删除、替换操作,每种操作代价均为1,求将word1变为word2的最少操作次数。

第一步,建模。设dp[i][j]表示word1的前i个字符变成word2的前j个字符需要的最少操作数。

第二步,找状态转移方程。当word1[i-1] == word2[j-1]时,最后一个字符不需要操作,dp[i][j] = dp[i-1][j-1]。当不相等时,有三种选择:

  • 替换:dp[i-1][j-1] + 1;
  • 在word1末尾插入一个与word2[j-1]相等的字符:dp[i][j-1] + 1;
  • 删除word1[i-1]:dp[i-1][j] + 1。

取三者最小值,就是dp[i][j]的值。完整方程写出来是:

dp[i][j] = min(dp[i-1][j-1] + cost, dp[i][j-1] + 1, dp[i-1][j] + 1)

其中cost在字符相等时为0,不等时为1。其实这个式子也能写成: if word1[i-1] == word2[j-1]: dp[i][j] = dp[i-1][j-1] else: dp[i][j] = 1 + min(dp[i-1][j-1], dp[i][j-1], dp[i-1][j])

第三步,初始化。dp[0][0] = 0,dp[i][0] = i,dp[0][j] = j。这个含义是:一个空串变成另一个串,只能靠不断插入或删除。

第四步,写复杂度。两层循环,时间O(mn),空间O(mn)。卷子上我建议把完整状态转移表画一个3×3的小例子出来,这能直观展示你理解了这个过程,也是很多老师设置的给分点。

接下来我想特别提一下背包问题的变体。期末题考纯裸的01背包概率不大,它通常会和"方案个数""恰好装满""物品可重复使用"混在一起。如果你看到"恰好装满"四个字,就要想到把dp数组初始化为负无穷,只让dp[0]为0;看到"方案个数"就要把max改成求和;看到"物品可以重复取"就是完全背包,要把内层循环倒过来。这几个变体之间的差异特别小,但踩错一个就是整道题全错。我建议复习时把这几个变体做成一张对照表,考前专门过一遍。

3. 贪心类题目:看着简单,证明才是真正的分水岭

3.1 识别贪心题的几个信号

贪心题和动态规划题在题干上高度相似,也经常求最值,很多同学在考场上分不清。我自己总结出的经验是:贪心题有一个特点,它在每个阶段做局部最优选择时,不需要考虑之前的决策如何修正。换句话说,贪心是"当场决定,绝不后悔",DP是"先算清所有可能性,再选最优的那条路"。

具体的识别信号包括:活动安排(会议室占用、节目安排)、任务调度(排定任务顺序使总代价最小)、哈夫曼编码(合并石子、最优前缀编码)、部分背包(物品可以切割时用贪心,不可切割时是0-1背包应该用DP)。这些题从直觉上说就是"先把好做的做了,后面的自然顺了",适合用贪心。

期末卷子上如果出现贪心题,出题人通常会选择一个方便你证明正确性的模型。活动安排就是最典型的例子:给定若干个区间,每个区间有开始时间和结束时间,求最多能选多少个互不重叠的区间。它的解法非常简洁,按结束时间从小到大排序,依次选择与前一个已选区间不重叠的区间。

3.2 贪心正确性证明有多重要:以活动安排问题为例

可能有人觉得,写出"按结束时间排序"几个字就完事了。但期末阅卷的给分点里,正确性证明往往占四到六分,甚至比排序本身的分还高。这也是吉大这门课的期末题与那些求职刷题网站的最大区别——刷题网站只看结果,期末卷子看你的思维过程。

活动安排问题的正确性证明我用的是交换论证法,写出来大致是这样:

设贪心算法选出的第一个活动是a,其结束时间为f[a]。最优解OPT中按结束时间排序后的第一个活动为b,那么f[a] <= f[b],因为a是所有活动中结束时间最早的。用a替换OPT中的b,得到的解仍然是合法解(因为a结束更早,给后续活动留下的时间更多),而且活动数量不变。重复这个替换过程,可以把OPT逐步转换成贪心解而不减少活动数量,因此贪心解至少与最优解一样好。证毕。

这个证明过程看起来有点绕,但它的核心就一句话:"每一步贪心选择都能替换到某个最优解里而不破坏最优性。"考场上把这个逻辑捋清楚,比背任何模板都管用。

3.3 涂色问题、任务调度这类"伪贪心"陷阱

做往年题的时候还发现一个出题人特别爱设置的陷阱,就是诱导你用贪心,但实际上贪心是错的。最典型的就是区间图着色问题变体——比如安排若干场考试,每个学生可能报多门课,考试时间不能冲突,问最少需要几个时间段。这个问题的正确思路是把它建模成图着色问题,贪心算法虽然能找到一种合法着色,但不能保证使用的颜色数最少。期末如果考这种题,正确解法通常是把区间转化为图,用Dijkstra前驱关系或者按某种策略的顺序着色,并辅以特定的证明条件。

碰到这种陷阱的时候,我建议大家先冷静三秒,问自己一个问题:如果每个阶段选局部最优,会不会出现"一步错步步错"的情况?如果存在这种可能,那就不是贪心,而是动态规划或图论问题。这个思维刹车在考场上特别管用,能帮你避掉很多出题人精心埋的雷。

3.4 期末卷子上贪心题的作答模板

我摸索出一个比较稳妥的贪心题回答结构,依次写四块:

  1. 贪心策略:用一句话说清楚每一步选什么、按什么标准选;
  2. 正确性证明:先用某种排序或度量说明贪心选择是安全的,再说明问题具有最优子结构;
  3. 算法步骤:用自然语言+伪代码描述;
  4. 复杂度分析:排序的O(n log n)加上扫描的O(n)。

这样写的好处是条理清晰、给分点全覆盖。即使你的证明比标准答案简略,阅卷老师也能从结构上看出你确实掌握了贪心算法的精髓,而不是在碰运气。

4. 图论大题:建模能力就是一分一分的积累

4.1 从场景到图:一个万能的三步翻译法

图论题在期末第五道大题里出现的频率也很高,而且它特别喜欢和实际场景结合。比如给出一堆城市和道路,问最短路线;给出一堆课程和先修关系,问是否排得出学习顺序;给出一堆网络节点和带宽,问最大传输量。出题人把算法名字藏起来,需要你自己把场景翻译成图。

我做图论题总结了三步翻译法,屡试不爽:

第一步,确定节点。"什么是一个基本单元",城市、课程、网络设备、人、物品,这些都是候选节点。 第二步,确定边。"两个基本单元之间有什么关系",道路、先修、通信链路、好友关系、货物运输线。 第三步,确定边权。"这个关系的量化属性是什么",距离、耗时、带宽、代价、容量。

举个例子,题目描述"某大学要在多个校区之间建设光纤网络,已知每两个校区之间的建设成本,要求让所有校区连通的总建设成本最小"——第一步节点是校区,第二步边是校区间的光纤连接,第三步边权是建设成本。问题翻译过来就是在带权无向图中找最小生成树,用Kruskal或Prim算法。我敢说,认真做过一次这种翻译训练的人,考场上读题的速度能快一倍。

4.2 最小生成树、最短路径、拓扑排序:各自什么时候登场

图论算法看起来很多,但期末第五道大题真正高频的就那么几个。我按出现频率和识别特征整理了一张对照表,这张表在我自己复习时贴在书桌前很久:

题型特征使用的算法核心思路
需要连通所有节点且代价最小Kruskal或Prim选边/选点,局部最优
单源点到其他所有点的最短路径Dijkstra(正权)/ SPFA或Bellman-Ford(负权)贪心+松弛
所有节点对之间的最短路径FloydDP三重循环
有向无环图的依赖顺序拓扑排序入度为0的节点先处理
有向图是否存在环DFS或拓扑排序检测回边或入度归零的个数
二分图的最大匹配匈牙利算法增广路

你可以发现一个细节:Dijkstra本质上就是一种贪心,而Floyd本质上就是动态规划。这说明这门课的算法思想是互相交织的,第五道大题也常常把两种思想放在同一个问题里考察。比如有一类题,先拓扑排序确定处理顺序,再在拓扑序上做DP求最长路径,这种题型在"任务调度求最短完成时间"的题目里非常常见。如果你能识别出这种组合结构,就已经比大部分人强了。

4.3 一个完整的Dijkstra变体验证:当图里有多个起点和终点

我们复习时练过一道很有意思的变形题,我觉得很贴近期末风格:有一个地铁系统,图里有n个车站,m条线路,每条线路有行驶时间。你想从家里到学校,但家附近有若干个可进站的地铁口,学校附近也有若干个可出站的地铁口,问最早到达学校的时间是多少。

如果直接对每个进站口跑一次Dijkstra,再把结果取最小值,那复杂度会乘以站点数,输入规模大一点就容易超时。标准做法是加一个超级源点,把超级源点到每个进站口的边权设为0,从超级源点跑一遍Dijkstra;再加一个超级汇点,每个出站口到超级汇点的边权设为0,这样求超级源点到超级汇点的最短路径就是答案。这个"超级源点"的思路在很多图论题里都是关键一步——它把"多个起点"的查询巧妙地降成一个起点,复杂度从O(k * (V log V))降到了O(V log V)。

考场上如果遇到这种变体,你只要把"加超级源点、超级汇点,边权设为0"这一句话写出来,阅卷老师立刻就知道你理解了这个算法背后的思想,而不是仅仅背会了模板。

4.4 图论题的卷面呈现:画图比写一千字管用

期末作答时,尽量在草稿纸上先画一个小的示例图,然后把你的建图方式、边的方向、权值标注都写清楚。很多同学习惯性只用文字描述,但图论题是个例外——一张标注清楚的示例图,能让阅卷老师一眼看懂你的建模逻辑,还能避免文字描述产生歧义。

我个人的推荐作答格式是:第一段写"我将问题建模为有向/无向图,节点代表XX,边代表XX,边权代表XX";第二段写出选用的算法名称和理由;第三段写轮数和伪代码;最后写复杂度分析。这个结构在期末卷面上非常吃香。

5. 考场上最容易踩的坑:我用扣分换来的教训

5.1 伪代码写作规范:不是让你写程序

期末卷子通常允许用伪代码,但很多同学写着写着就写成了完整程序——变量声明、类型定义、输入输出格式全写了,反而把核心逻辑淹没在大量细节里。

伪代码的正确写法是"人类能看懂、机器不一定要能运行"。逻辑分支用中文描述都可以,比如"若当前字符相等,则直接继承左上角的值"。关键是你得让阅卷老师看清楚:你每一步在做什么、状态怎么转移、循环的边界是什么。

我现在建议所有准备这门课的同学养成一个习惯:写伪代码时只保留算法核心要素——状态定义、初始化、转移方程、复杂度。其他全部砍掉。这个习惯练出来之后,考场作答速度能快不少。

5.2 时间分配:第五道大题值得你留足40分钟

很多同学觉得第五道大题难,就把它放在最后做,结果往往只留下十几分钟,草草写两句模型分析就交卷了。我的教训是:这道题恰恰应该放在所有非选择题中的第二位做。

原因很简单,它的分值高、给分点分散。就算你最后算法没设计完整,只要状态定义写对了、DP方程写出来一部分,也能拿到不少分。但如果时间不够,你连状态定义都没机会写,那这20分就是零。我模拟考时试过两种顺序,最终验证下来,"先做有把握的大题,再做第五道大题,最后做剩下需要大量计算的题"是得分最稳的。考前至少留40分钟给第五道大题,你会发现自己能多写很多内容。

做题顺序的具体安排可以这样:拿到卷子花3分钟通读全卷,标出每道题的类型和难度;先做选择和填空题,快速拿稳基本分;然后直接做第五道大题,趁脑子还清醒、时间还充裕,把思路理清楚;做完后回头做前面的大题;最后检查一遍。当然,每个人习惯不同,但核心原则是一样的:把最高分的题放在你精力最好的时间区间内。

5.3 复杂度分析的标准写法:别漏掉任何一个循环

期末的算法设计题,复杂度分析一般占3到5分。很多同学写错不是因为不会算,而是漏掉步骤。我给大家一个标准流程:先数循环层数,再说每层循环的规模,最后乘起来;递归算法则要写递推式,比如T(n) = 2T(n/2) + O(n),然后说明结果是O(n log n)。

空间复杂性也别忘记。用了二维数组就说O(mn),用了滚动数组就说O(n),注意别和小问里的其他变量搞混。一个小技巧是:在伪代码里用注释标出每一段的时间消耗,最后汇总。比如"// 排序O(n log n)"、"// 主扫描循环O(n)",这样既帮助自己理思路,也方便阅卷老师找到给分点。

5.4 遇到完全没有头绪的题:保底策略

万一真遇到一道不会的题,不要空着。我的保底策略是三步:

第一步,把题目的场景用数学语言重新表述一遍,哪怕只是"设dp[i]表示前i个元素的最优解"这种状态定义的雏形; 第二步,写一个最简单的暴力解法,比如暴力枚举所有组合,说明复杂度是O(2^n); 第三步,如果能优化,就写"考虑使用动态规划/贪心优化"以及一个大概的想法,哪怕不完整。

这种"写了就可能给分"的策略在期末非常实用。算法设计题有步骤分,你让阅卷老师看到你在朝正确方向思考,他会给你该拿的分。千万不要因为不确定就留白。

6. 从复习到上考场:最后的冲刺建议和一点个人经验

6.1 复习阶段不要光看"会做了",要动手写完整答案

我见过太多同学复习算法题的方式是:看到一道题,想了想思路,觉得很对,然后匆匆翻到答案比对,发现一致,于是下一题。这种复习方式效率很低,因为考场上要写的是完整的文字、公式、表格和伪代码,你想清楚和写清楚之间隔着很大一段距离。

我的建议是,考前至少完整动笔写15道大题的完整答案,包括建模过程、状态定义、转移方程、伪代码、复杂度分析、正确性证明。写成之后对照标准答案给自己打分,尤其注意那些"我以为对了但其实写漏了"的小地方。这15道题写下来,你对第五道大题的套路基本就烂熟于胸了。

6.2 最后两周的时间安排:题型滚动比题海战术更有效

算一算从复习开始到考试的时间,通常两到三周是效率最高的时间窗口。这期间我不推荐疯狂刷新题,因为期末第五道大题的考点范围有限,你更需要的是"题型熟练度"而非"新题见识量"。我当时采用了一个滚动复习的计划,效果不错:

第一周,按题型板块刷题——分治一到两天、动态规划三到四天、贪心跳过一天、图论两到三天。每块只挑代表性题目,每个题型的经典题做一道、变形题做一道。 第二周,开始做整卷模拟,严格卡时间。模拟卷用期末历年题或自己组合的题目,重点是训练"时间分配+完整作答"。 最后两三天,只看错题和错题旁边的批改笔记,不再碰新题。考前把各类算法的核心代码框架过一遍。

这个安排的好处是既有广度又有深度,既积累了题型经验又训练了卷面书写速度。

6.3 一个关于"复习深度"的个人体会

如果你现在就坐在图书馆里为这门课焦虑,我想说几句掏心窝子的话。算法分析与设计这门课,学得好不好,最后一道大题是真正的试金石。它考察的不是你记了多少模板,而是你能不能像一个工程师一样,把一个问题拆分、建模、选择算法、实现、验证、分析。这个思维过程,比期末考本身珍贵得多。

我当年复习时把课本习题和历年真题做了一遍又一遍,最大的收获并不是考试多考了几分,而是后来到了写代码做项目的阶段,遇到复杂问题时会本能地先想"这个问题的状态是什么、转移是什么、复杂度能不能接受"。这种思维方式一旦建立起来,你就再也不会觉得算法是门"没用"的课了。

最后给一个最实在的建议:把这份汇总里的题型列表存下来,每复习完一个题型就在旁边打个勾,然后自己动手写一道该题型的完整答案贴在一侧。等你把每个题型都过完一遍再回头看,你会发现自己已经不怕第五道大题了——它其实就是出题老师给你的一张藏宝图,规律都藏在题型识别和答题结构里,就看你想不想花时间去挖。

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

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

立即咨询