考试时间:2026 年 1 月 19 日 19:00-21:00,最后一门期末考试
最后一门期末考试!17 号考完 ICS 后开始同时复习离散数学和算法基础。先把 topic 给过了一遍。然后对着数据结构没有复习到的点逐一复习,首先把 P/NP 东西搞懂(比较感兴趣),然后逐一击破知识点。照例是扔给 notebooklm 来做的。算基有许多往年卷,看了两套。考前吃了麦当劳,在飞雪的夜晚边听歌边暴走着放松情绪。考场上判断和大题很快就看过去了。判断错 2 道,势能法不会,都是一些没看到没做过的知识点。后面算法设计题蒙出来一道多。
考完下着雪,地上已经有一层了。高新食堂窗户上写着 SBMLA。打车去吃烧鸟,提灯味道比较腥感觉,还是烤肉和寿喜锅好吃!
考试范围#
自己对照 AI 和老师的 PPT 梳理了一下,感觉算法基础这门课品味不错。使用高级的数据结构建模问题,从最基础的递归、动态规划、贪心、分治法出发,能创造出很多新奇的思路来解决问题,并可以用数学来证明结果的正确性
Topic:
- 算法复杂度分析:主定理、递归式求解、摊还分析
- 排序:比较排序(插入等 4 个,堆,快排)、线性时间排序(基数,计数,桶)、顺序统计量
- 高级数据结构:二叉排序树、红黑树、斐波那契堆、并查集;数据结构扩张
- 算法设计与分析:动态规划、贪心算法、摊还分析、分治法
- 图算法:最小生成树、单源最短路径、所有节点对最短路径、最大流
- 字符串算法:朴素算法、Rabin-Karp、有限自动机、KMP 算法
- 高级专题:NP 完全性,近似算法
如果您在准备考试,主定理的计算、网络流的 Ford-Fulkerson 手动模拟(画残存网络)、KMP 的 next 数组计算、以及 P/NP/NPC 的概念辨析是历年必考的重中之重。
Topic 1-4#
红黑树:根是黑的;叶子 (NIL) 是黑的;红节点的子节点必黑(不连续红);从任一节点到其后代叶子的所有路径包含相同数目的黑节点
Topic7#
P/NP 概念类:
- P:多项式时间内能解决。欧拉回路
- NP:多项式时间内能验证这个解是否正确
- 约化:如果问题 A 可以在多项式时间内约化为问题 B,那么如果能解决 B,就一定能解决 A。B 的难度大于等于 A 的难度。记为 A≤pB。
- 约化链:3-SAT(子语句是否为真)< 团问题 (无向图 G 中是否有完全子图 k) < 顶点覆盖(G 中是否有顶点子集,每个端点在子集中) < 哈密顿回路 < 旅行商问题 TSP
- NP-Hard:如果所有 NP 问题能在多项式时间内归约到 NP-Hard 问题。NP-Hard 不一定属于 NP
- NPC 问题:既是 NP 也是 NP-Hard。
近似算法
- 近似比:解的代价 C 与最优解的代价 C*的比值
- 常见近似算法:
- 顶点覆盖:2-近似算法。任选一条边 (u, v),将 u, v 都加入覆盖集 C,图中删去 u, v 相关联的所有边,重复直到没有边。该 C 是最优解 C 的至多两倍
- 旅行商问题,满足三角不等式:2-近似算法。找到最小生成树,先序遍历,按先序遍历,直接跳到新点。证明:MST 权重 ≤ 最优 TSP 权重 ≤ 2 MST 权重(一去一回)
- 子集覆盖问题:从子集 S 选出最少的集合,能覆盖全集的元素 U。贪心算法:每次选择能覆盖剩余元素个数最多的子集。近似比 lnn。
- 近似方案:PO(多项式时间能找到最优解) ⊆ FPTAS (n 与误差倒数的多项式)⊆ PTAS(n 的误差倒数指数次方的多项式) ⊆ APX(常数近似) ⊆ log-APX ⊆ NPO
0/1 背包问题:伪多项式时间算法 O(nW) W 为背包容量。多项式时间:运行时间是输入的比特位数 O(logW) 的多项式函数。伪多项式时间:是输入的数值的多项式函数
动态规划#
使用动态规划时,问题必须要拥有最优子结构、重叠子问题。DP 通过自顶向下或备忘录来避免重复运算。 关键:状态定义、状态转移方程。无后效性:算后面的时候,前面已经算好了 复杂度:多项式
- 线性模型:序列 DP,一维数组
- 网格模型:二维数组
- 区间 DP:矩阵乘法
- 背包问题:需要枚举背包容量
0-1 背包问题#
物体不可分时。物体可分:使用贪心
- 状态定义:
dp[i][w]:在容量为 w 的情况下,在只能从前 i 个物品选择中,能选到的最大价值 - 状态转移:走到 i 物品时,若不拿 i 物品,则价值为
dp[i-1][w]。若拿走 i 物品,且背包装得下,则价值为dp[i-1][w-w_i] + v_i。因此 dp[i][w]=max(dp[i−1][w],dp[i−1][w−wi]+vi) - 算法执行流程:填表法。初始化 0,外层循环遍历 0 到 i,内层循环遍历 0 到 w。若 w < w_i,则装不下,
dp[i][w] = dp[i-1][w]。否则用上面式子取最大值。
最大子数和: Kadane 算法#
求解数组中最大连续子数组的和的最大值
- 初始化:
dp[i]当前位置结束的位置和,maxSoFar整个数组的和 - 状态转移:
dp[i] = max(nums[i], dp[i] + nums[i]):当前位置结束的和 = 前面数组与当前元素 + 从当前元素开始的和 maxSoFar = max(maxSoFar, dp[i]):全局最大和 时间复杂度:O (n)
1def maxSubArray(nums):2 maxEndingHere = nums[0]3 maxSoFar = nums[0]4 for i in range(1, len(nums)):5 maxEndingHere = max(nums[i], maxEndingHere + nums[i])6 maxSoFar = max(maxSoFar, maxEndingHere)7 return maxSoFar8# 示例9nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]10print("最大子数组和为:", maxSubArray(nums)) # 输出: 6LIS:最长递增子序列#
- 想法:最大递增序列 = max 以 i 结尾的最大递增序列。后者可以用 dp 表简单给出
- 状态定义:
dp[i]表示以 i 结尾的最长递增子序列长度 - 状态转移:对于 i,遍历 j = 0 到 i-1。若
a[i] > a[j],则dp[i] = 1 + max(dp[j])。
1def lengthOfLIS(nums):2 if not nums:3 return 04 dp = [1] * len(nums)5 for i in range(len(nums)):6 for j in range(i):7 if nums[i] > nums[j]: # 这个条件可以变掉8 dp[i] = max(dp[i], dp[j] + 1)9 return max(dp)时间复杂度优化#
- 想法:
- 红黑树等数据结构优化:
n => lgn - 最大化递增子序列长度 => 最小化递增子序列末尾项 。维护数组
tail[i],表示长度为 i 的所有递增子序列中,末尾最小的元素。Tail 数组严格自增,但是不一定是最后数组
- 红黑树等数据结构优化:
- 流程:for x in array:
x > tail[end]:x 追加到 tail 末尾x <= tail[end]:使用二分查找,在 tail 中找到第一个大于等于 x 的数并用 x 替换。
- 时间复杂度:O(n lgn)
1import bisect2def lengthOfLIS(nums):3 tails = []4 for num in nums:5 idx = bisect.bisect_left(tails, num) # bisect_left 寻找第一个 >= num 的位置6 if idx == len(tails): # 如果位置等于当前长度,说明 num 比 tails 所有元素都大7 tails.append(num)8 else: # 否则,替换掉该位置的元素,让该长度的子序列结尾更小9 tails[idx] = num10
11 return len(tails)给定一个长度为 n 的序列 a1, a2, · · · , an,求长度为 k 的严格递增子序列的个数,要求时间复杂度 O (nk log n)。
解:dp[i][j] 表示以第 i 个数字结尾,长度为 j 的最大递增子序列。
然后再用红黑树优化
LCS:最长公共子序列#
给定 X 和 Y,找出共同的最长子序列
- 状态定义:
c[i,j]表示 X 的前 i 个字符与 Y 的前 j 个字符最长公共子序列的长度 - 状态转移:
- 若
x[i] == y [i]:两字符相等,c[i,j] = c[i-1,j-1] + 1 - 若不等于,说明不能作为公共结尾。抛弃 x 的末尾和 y 的末尾看看。
c[i,j] = max(c[i-1,j], c[i,j-1] )取最大值。若某一项为 0 则为 0(边界条件)
- 若
矩阵链乘法#
区间 DP:大区间的解是小区间合并出来的。初始化区间=1,先枚举区间长度 len,再枚举起点 i,最后计算终点 j。在区间内枚举分割点 k<len,寻找最优位置
矩阵链乘法:
- 状态定义:
m[i,j]从 A_i 到 A_j 所需最少乘法数 - 状态转移:m[i,j]=mini≤k<j(m[i,k]+m[k+1,j]+pipjpk). 其中
m[i,i] = 0。
最优搜索二叉树:有关键字 e1, e2… En 及其出现的概率 p1 p2 pn,构造二叉树使之查找效率最高。最优树的子树也是最优树,具有最优子结构
- 状态定义:
e[i,j]表示 i 到 j 的最优搜索代价 - 状态转移:e[i,j]=mini≤k<j(e[i,k−1]+e[k+1,i]+w(i,j)),
w(i,j)表示子树内所有概率的和。当选取根节点接入子树时,会使所有节点深度都加 1.
下面是习题课记的一些东西
状态转移和树与图#
一个公司有 n 个员工,每个员工(除了老板)都有一个直属上司。现在要举办一个晚宴,晚宴邀请到第 i 个员工会增加快乐值 vi,但是如果一个员工的直属上司来参加了,那么这个员工就无论如何也不肯来参加。问总的快乐值最大能够是多少。要求时间复杂度 O (n)。
上司舞伴问题:在图上的状态转移方程
最长链问题/最长链技术问题:求有向无环图中顶点数最多的路径长度和路径树?
解:先用拓朴排序,然后计算 f[u] :到达 u 最长链的长度
方案数:再用 g[u] 数组旧仍以一个点到 u 的最长链的条数,状态转移方程:
给定一张 n 个点 m 条边的有向图(不一定是 DAG)。每个点有权值。求一条路径,使路径经过的点权值之和最大。允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。要求时间复杂度 O (n + m)。
强连通分量:如果经过强连通分量的一个点,则可以遍历强连通分量的任何点
贪心算法#
0/1 背包问题,要求设计一个近似比为 1/ 2 的贪心算法。要求时间复杂度为 O (n log n)。 每个物品有体积 vi,价值 wi,按价值密度从大到小排序物品,
分治#
给定平面上 n 个点,要求输出距离最短的两个点,时间复杂度 O (n lg n)
- 预处理:按 x 坐标对所有点排序,得到数组 Px
- 分治:用中位 x 将平面分为两半。递归计算左半边最小值 δl 和右半边 δr,最小值记为 δ
- 跨越中线的最近点对:只考虑距离中线小于等于 δ 的点,组成集合 S,将 S 按 y 轴排序。对于 S 中每个点,只和前后 k 个点进行比较。可证明 k 是常数
1solve(r,l):2 solve(r,mid)3 solve(mid,l)