递归、递推与 DP¶
方法速览¶
| 任务 | 方法 | 口诀 |
|---|---|---|
| 手算递归 | 代入展开成递归树,叶子的值逐层回代 | 展开再回收 |
| 递归层数过多 | 栈空间溢出(每层一帧) | 递归吃栈 |
| 朴素递归慢的原因 | 重叠子问题被重复计算 | 重算子问题 |
| 分治递归式 | 展开求和:\(T(n)=2T(n/2)+O(n^2)\) → \(O(n^2)\) | 层层求和 |
| 期望/平均值 | 全部情形的收益总和 ÷ 情形数 | 枚举求平均 |
手算递归¶
【是什么】 把函数调用按定义展开成调用树,先算叶子再逐层回代。
【怎么算】
- 按定义一层层展开到边界;
- 从最内层算起,值向外传。
【例】(2022 真题)Ackermann 函数求 ack(2,2):
| 步骤 | 展开 |
|---|---|
| 1 | \(ack(2,2) = ack(1, ack(2,1))\) |
| 2 | \(ack(2,1) = ack(1, ack(2,0)) = ack(1, ack(1,1))\) |
| 3 | \(ack(1,1) = ack(0, ack(1,0)) = ack(0, ack(0,1)) = ack(0,2) = 3\) |
| 4 | \(ack(2,1) = ack(1,3) = 5\) |
| 5 | \(ack(2,2) = ack(1,5) = 7\) |
规律:\(ack(1,n) = n+2\)、\(ack(2,n) = 2n+3\)——验证 \(ack(2,2) = 7\) ✓。
【例】(2021 真题)solve(t, n):\(t=1\) 返回 1,否则返回 \(5\cdot solve(t-1,n) \bmod n\)。\(solve(23,23) = 5^{22} \bmod 23\),由费马小定理(23 是质数)\(5^{22} \equiv 1 \pmod{23}\) → 1。
递归的代价¶
- 栈溢出:每层调用占一帧栈(参数、局部变量、返回地址),层数过多打爆系统分配的栈空间——不是队列/链表/堆(2021 原题);
- 重叠子问题:朴素递归斐波那契 \(F(n)\) 里 \(F(n-2)\) 被算两遍、越往下越多——所以朴素版 \(O(2^n)\)、DP 版 \(O(n)\),差距根源是重复计算(2025 原题),与调用栈开销无关。
递推与分治递归式¶
【例】(2024 真题)\(f(1)=1\),\(f(n) = f(n-1) + f(\lfloor n/2\rfloor)\):
| n | 演算 | f(n) |
|---|---|---|
| 1 | 边界 | 1 |
| 2 | \(f(1)+f(1)\) | 2 |
| 3 | \(f(2)+f(1)\) | 3 |
| 4 | \(f(3)+f(2)\) | 5 |
递归式展开求和(2025 真题):\(T(n) = 2T(n/2) + O(n^2)\):
| 层 | 子问题数 × 单个代价 | 该层合计 |
|---|---|---|
| 0 | \(1 \times n^2\) | \(n^2\) |
| 1 | \(2 \times (n/2)^2\) | \(n^2/2\) |
| 2 | \(4 \times (n/4)^2\) | \(n^2/4\) |
总和 \(n^2(1 + \tfrac12 + \tfrac14 + \cdots) \le 2n^2 = O(n^2)\)——顶层支配,别急着套主定理的 \(n\log n\)(那要合并代价是 \(O(n)\) 才成立)。
DP 手算:0-1 背包¶
【例】(2025 真题)容量 20,重量 \((7,5,4,3,6)\)、价值 \((15,12,9,7,13)\):
按 0-1 背包(容量倒序)滚动,最终 \(dp[20] = 44\)——枚举验证:取物品 1、3、4、5(重量 \(7+4+3+6 = 20\) 恰满,价值 \(15+9+7+13 = 44\))最优;取 2、3、4、5(重量 18、价值 41)差一截。
练习题目¶
练习 1 · 递归的展开¶
题目
int f(int n) { if (n == 1) return 1; return n * f(n - 1); } 则 f(4) =( )
- A. \(10\)
- B. \(24\)
- C. \(4\)
- D. \(16\)
答案与解析
答案:B
\(4 \times 3 \times 2 \times 1 = 24\)——阶乘的递归实现。
练习 2 · 双递归回代¶
题目
int g(int n) { if (n <= 2) return 1; return g(n-1) + g(n-2); } 则 g(6) =( )
- A. \(5\)
- B. \(6\)
- C. \(13\)
- D. \(8\)
答案与解析
答案:D
\(g(1..6) = 1,1,2,3,5,8\)——斐波那契。
练习 3 · 条件递归¶
题目
int h(int n) { if (n == 0) return 2; return h(n/2) + 1; } 则 h(8) =( )
- A. \(5\)
- B. \(4\)
- C. \(6\)
- D. \(3\)
答案与解析
答案:C
逐层回代:\(h(0)=2\),\(h(1)=3\),\(h(2)=4\),\(h(4)=5\),\(h(8)=6\)——除到底再逐层加回来。
干扰项思路:A 数错层数(少算一层)。
练习 4 · 栈溢出¶
题目
递归层数过多引发错误,是因为( )
- A. 堆空间溢出
- B. 队列空间溢出
- C. 系统分配的栈空间溢出
- D. CPU 过热
答案与解析
答案:C
每层调用占一帧栈帧;局部大数组 + 无限递归几层就爆。
练习 5 · 递归空间¶
题目
递归 n 层、每层只开 O(1) 局部变量,空间复杂度( )
- A. \(O(n)\)(栈深 n 层)
- B. \(O(1)\)
- C. \(O(\log n)\)
- D. \(O(n^2)\)
答案与解析
答案:A
空间看递归深度:n 层栈帧并存;时间看子问题数——两者别混。
练习 6 · 朴素递归复杂度¶
题目
朴素递归计算 \(F(n)\)(斐波那契)的时间复杂度是( )
- A. \(O(n)\)
- B. \(O(n \log n)\)
- C. \(O(2^n)\)
- D. \(O(n^2)\)
答案与解析
答案:C
每次分裂两个子调用,递归树节点数指数级(严格说是 \(O(\varphi^n)\),选项按 \(2^n\) 记)。
练习 7 · 快慢差距根源¶
题目
朴素递归斐波那契 \(O(2^n)\)、DP 迭代 \(O(n)\),巨大差异的根本原因是( )
- A. 递归调用栈开销过大
- B. 操作系统限制递归深度
- C. DP 用了更少内存
- D. 朴素递归存在大量重叠子问题被重复计算
答案与解析
答案:D
\(F(n-2)\) 被重复算了两次起步、越往下越爆炸;记忆化/DP 每个子问题只算一次。
练习 8 · 记忆化¶
题目
给递归加上“算过的存起来、再要直接取”的缓存后,斐波那契的复杂度变为( )
- A. \(O(\log n)\)
- B. \(O(2^n)\)
- C. \(O(n^2)\)
- D. \(O(n)\)
答案与解析
答案:D
\(F(1)\dots F(n)\) 各算一次、每次 \(O(1)\)——记忆化搜索与递推等价。
练习 9 · 递推手算¶
题目
\(a_1 = 2\),\(a_n = 2a_{n-1} + 1\),则 $a_4 = $( )
- A. \(23\)
- B. \(31\)
- C. \(15\)
- D. \(7\)
答案与解析
答案:A
逐项递推:\(a_2 = 5\)、\(a_3 = 11\)、\(a_4 = 23\)。
干扰项思路:C 是 \(2\times7+1\) 的漏步误算(把 \(a_3\) 错当 7)。
练习 10 · 走楼梯(等差消耗)¶
题目
第 \(k\) 到 \(k+1\) 层消耗 \(10k\) 卡,从 1 层爬起消耗满 1000 卡,至少爬到( )层。
- A. \(13\)
- B. \(14\)
- C. \(16\)
- D. \(15\)
答案与解析
答案:D
累计 \(10(1+2+\cdots+k) = 5k(k+1) \ge 1000\) → \(k(k+1) \ge 200\) → \(k = 14\)(\(14\times15 = 210\));到 \(1 + 14 = 15\) 层。
练习 11 · 递归式(顶层支配)¶
题目
\(T(n) = 2T(n/2) + O(n^2)\) 的解是( )
- A. \(O(n\log n)\)
- B. \(O(n^2)\)
- C. \(O(n^2 \log n)\)
- D. \(O(n^3)\)
答案与解析
答案:B
各层代价 \(n^2 + n^2/2 + n^2/4 + \cdots \le 2n^2\)——几何级数收敛,顶层支配。
练习 12 · 递归式(标准主定理形)¶
题目
\(T(n) = 2T(n/2) + O(n)\) 的解是( )
- A. \(O(n)\)
- B. \(O(n \log n)\)
- C. \(O(n^2)\)
- D. \(O(\log n)\)
答案与解析
答案:B
每层代价都是 \(n\)、共 \(\log n\) 层——归并排序的形状。
练习 13 · 递归式(单分支)¶
题目
\(T(n) = T(n/2) + O(1)\) 的解是( )
- A. \(O(1)\)
- B. \(O(\log n)\)
- C. \(O(n)\)
- D. \(O(\sqrt n)\)
答案与解析
答案:B
每次砍半只花 \(O(1)\)——二分查找的形状。
练习 14 · 快速幂的形状¶
题目
朴素的 pow(x, n) = x * pow(x, n-1) 的时间复杂度是( )
- A. \(O(n \log n)\)
- B. \(O(\log n)\)
- C. \(O(1)\)
- D. \(O(n)\)
答案与解析
答案:D
一次递归减一 → 链长 n;快速幂每次减半才是 \(O(\log n)\)——“减一 O(n)、减半 O(log)”。
练习 15 · 汉诺塔¶
题目
汉诺塔 \(T(n) = 2T(n-1) + 1\),\(T(1) = 1\),则 $T(6) = $( )
- A. \(63\)
- B. \(64\)
- C. \(31\)
- D. \(127\)
答案与解析
答案:A
通项 \(T(n) = 2^n - 1\):\(2^6 - 1 = 63\)。
练习 16 · 01 背包小算¶
题目
容量 8、物品 \((w,v) = (3,4), (5,6), (4,5)\) 的 0-1 背包最优价值是( )
- A. \(11\)
- B. \(9\)
- C. \(10\)
- D. \(8\)
答案与解析
答案:C
枚举组合:物品 1+2(\(w = 8\) 恰满,\(v = 4+6 = 10\))最优;1+3 得 9、2+3 超重。
练习 17 · 背包贪心失效¶
题目
0-1 背包不能按“价值/重量比”贪心求解的原因( )
- A. 物品不能拆分,高性价比物品可能挤占空间使整体不是最优
- B. 计算太慢
- C. 数据会溢出
- D. 没有原因,可以贪心
答案与解析
答案:A
每件不可分割是 0-1 的本性;可拆分(部分背包)才允许贪心。
练习 18 · LCS 转移¶
题目
LCS 中 \(x_i = y_j\) 时 $dp[i][j] = $( )
- A. \(\max(dp[i-1][j], dp[i][j-1])\)
- B. \(dp[i][j-1] + 1\)
- C. \(dp[i-1][j-1]\)
- D. \(dp[i-1][j-1] + 1\)
答案与解析
答案:D
字符相等接在左上角答案之后;A 是失配分支。
练习 19 · LCS 手算¶
题目
ABCAAAABA 与 ABABCBABA 的 LCS 长度是( )
- A. \(4\)
- B. \(5\)
- C. \(6\)
- D. \(7\)
答案与解析
答案:C
按 LCS 递推填 9×9 的 dp 表,程序验证结果为 6;手算容易在多个 A/B 交错处数多,列表格逐格填。
练习 20 · 递归调用次数¶
题目
朴素递归斐波那契计算 \(F(6)\)(\(F(1)=F(2)=1\))共发生( )次函数调用。
- A. \(15\)
- B. \(5\)
- C. \(31\)
- D. \(8\)
答案与解析
答案:A
调用数 \(C(n) = C(n-1)+C(n-2)+1\):\(C(1)=C(2)=1\) → 1,1,3,5,9,15;\(C(6) = 15\)。
练习 21 · 记忆化的调用数¶
题目
给上一题的递归加上记忆化后,计算 \(F(n)\) 的函数调用次数是( )
- A. \(O(n)\)(每个 \(F(i)\) 只算一次)
- B. \(O(2^n)\)
- C. \(O(n^2)\)
- D. \(O(1)\)
答案与解析
答案:A
缓存命中直接返回——从指数降到线性。
练习 22 · 费马小定理速算¶
题目
$5^{22} \bmod 23 = $( )
- A. \(22\)
- B. \(1\)
- C. \(5\)
- D. \(0\)
答案与解析
答案:B
23 是质数且 \(\gcd(5,23)=1\):费马小定理 \(5^{22} \equiv 1 \pmod{23}\)。
练习 23 · 模意义递归¶
题目
\(f(1) = 1\),\(f(n) = 3f(n-1) \bmod 7\),则 $f(4) = $( )
- A. \(2\)
- B. \(3\)
- C. \(6\)
- D. \(5\)
答案与解析
答案:C
逐步取模:\(f(2) = 3\)、\(f(3) = 2\)、\(f(4) = 6\)。
干扰项思路:D 是忘了最后一步取模。
练习 24 · 圆环完全平方(模拟)¶
题目
4 根柱子,相邻圆环编号和必须是完全平方数,从 1 号开始依次放,最多放( )个。
- A. \(9\)
- B. \(11\)
- C. \(7\)
- D. \(13\)
答案与解析
答案:B
贪心模拟(每环放第一根能放的柱子):放完 11 号后,12 与各柱顶(放不下)——程序模拟验证 11。
练习 25 · 递归与迭代的等价¶
题目
任何递归算法( )改成迭代。
- A. 都可以(用显式栈模拟递归栈)
- B. 只有树递归可以
- C. 不可以
- D. 只有简单递归可以
答案与解析
答案:A
递归的本质是系统帮你压栈;自己开个栈就能完全模拟。
练习 26 · 重叠子问题识别¶
题目
下列递归中没有重叠子问题的是( )
- A. 斐波那契 \(F(n) = F(n-1) + F(n-2)\)
- B. \(g(n) = g(n-1) + n\)
- C. 组合数 \(C(n,k) = C(n-1,k-1) + C(n-1,k)\)
- D. 朴素递归 LCS
答案与解析
答案:B
\(g\) 是单链递归,每个 \(g(i)\) 只被算一次;A/C/D 的子调用会大量交叠重复。
历年真题¶
2020 年 · 第 11 题¶
题目
小明想通过走楼梯来锻炼身体,假设从第 \(1\) 层走到第 \(2\) 层消耗 \(10\) 卡热量,接着从第 \(2\) 层走到第 \(3\) 层消耗 \(20\) 卡热量,从第 \(3\) 层走到第 \(4\) 层消耗 \(30\) 卡热量,依此类推,从第 \(k\) 层走到第 \(k+1\) 层消耗 \(10k\) 卡热量(\(k>1\))。如果小明想从 \(1\) 层开始,通过连续向上爬楼梯消耗 \(1000\) 卡热量,至少要爬到第几层楼?( )
- A. \(14\)
- B. \(16\)
- C. \(15\)
- D. \(13\)
答案与解析
答案:C
累计消耗 \(10(1+2+\cdots+k) = 5k(k+1)\):\(k=13\) 时 \(910 < 1000\),\(k=14\) 时 \(1050 \ge 1000\) → 爬过 14 段,到 \(1+14 = 15\) 层。
2021 年 · 第 3 题¶
题目
在程序运行过程中,如果递归调用的层数过多,可能会由于( )引发错误。
- A. 系统分配的栈空间溢出
- B. 系统分配的队列空间溢出
- C. 系统分配的链表空间溢出
- D. 系统分配的堆空间溢出
答案与解析
答案:A
递归帧压在栈上,层数过多即栈溢出。
2021 年 · 第 11 题¶
题目
有如下递归代码:
则 solve(23,23) 的结果为( )。
- A. \(1\)
- B. \(7\)
- C. \(12\)
- D. \(22\)
答案与解析
答案:A
展开后是 \(5^{22} \bmod 23\);由费马小定理(质数 23)得 \(5^{22} \equiv 1 \pmod{23}\)。
2021 年 · 第 12 题¶
题目
斐波那契数列的定义为:\(F_1=1\),\(F_2=1\),\(F_n=F_{n-1}+F_{n-2}\)(\(n\ge3\))。现在用如下程序来计算斐波那契数列的第 \(n\) 项,其时间复杂度为( )。
- A. \(O(n)\)
- B. \(O(n^2)\)
- C. \(O(2^n)\)
- D. \(O(n\log n)\)
答案与解析
答案:C
每次调用分裂两个子调用,递归树指数级膨胀。
2022 年 · 第 15 题¶
题目
ack 函数在输入参数 (2,2) 时的返回值为( )。
- A. \(5\)
- B. \(7\)
- C. \(9\)
- D. \(13\)
答案与解析
答案:B
逐层展开(完整表见教学节):\(ack(2,2) = ack(1,5) = 7\);规律 \(ack(2,n) = 2n+3\)。
2023 年 · 第 4 题¶
题目
假设有 \(n\) 根柱子,需要按照以下规则依次放置编号为 \(1,2,3,\ldots\) 的圆环:每根柱子的底部固定,顶部可以放入圆环;每次从柱子顶部放入圆环时,需要保证任何两个相邻圆环的编号之和是一个完全平方数。请计算当有 \(4\) 根柱子时,最多可以放置( )个圆环。
- A. \(7\)
- B. \(9\)
- C. \(11\)
- D. \(5\)
答案与解析
答案:C
模拟:每号放第一根满足“与顶和为完全平方”的柱子;1 空放、2 与 1 和 3 ✓、3 与 1 和 4 ✓、4 与 2 和 6 ✓……到 11 号(与各柱顶都不构成平方数)卡住——程序模拟验证 11。
2023 年 · 第 7 题¶
题目
最长公共子序列长度常常用来衡量两个序列的相似度。给定两个序列 \(X=\{x_1,x_2,x_3,\ldots,x_m\}\) 和 \(Y=\{y_1,y_2,y_3,\ldots,y_n\}\),最长公共子序列(LCS)问题的目标是找到一个最长的新序列 \(Z\),使得 \(Z\) 既是 \(X\) 的子序列,又是 \(Y\) 的子序列,且长度最大。(序列 \(A\) 是 \(B\) 的子序列,当且仅当保持 \(B\) 的元素顺序删除若干元素后可构成 \(A\)。)
序列 ABCAAAABA 和 ABABCBABA 的最长公共子序列长度为( )。
- A. \(4\)
- B. \(5\)
- C. \(6\)
- D. \(7\)
答案与解析
答案:C
按 LCS 递推填表(程序验证)= 6。
2024 年 · 第 3 题¶
题目
在 C++ 中,以下哪个函数调用会造成栈溢出?( )
- A.
int foo() { return 0; } - B.
int bar() { int x = 1; return x; } - C.
void baz() { int a[1000]; baz(); } - D.
void qux() { return; }
答案与解析
答案:C
无递归出口的自调用 + 每帧 4KB 数组,几层就打爆默认栈空间(与 2021 年第 3 题同考点)。
2024 年 · 第 6 题¶
题目
已知 \(f(1) = 1\),且对于 \(n \ge 2\) 有 \(f(n) = f(n - 1) + f(\lfloor n/2 \rfloor)\),则 \(f(4)\) 的值为( )。
- A. \(4\)
- B. \(5\)
- C. \(6\)
- D. \(7\)
答案与解析
答案:B
\(f(2) = f(1)+f(1) = 2\);\(f(3) = f(2)+f(1) = 3\);\(f(4) = f(3)+f(2) = 5\)。
2025 年 · 第 9 题¶
题目
一个 \(0\)-\(1\) 背包问题,背包容量为 \(20\)。现有 \(5\) 个物品,其重量和价值分别为 \(7, 5, 4, 3, 6\) 和 \(15, 12, 9, 7, 13\)。装入背包的物品能获得的最大总价值是多少?
- A. \(43\)
- B. \(41\)
- C. \(45\)
- D. \(44\)
答案与解析
答案:D
取物品 1、3、4、5:重量 \(7+4+3+6 = 20\) 恰满、价值 \(15+9+7+13 = 44\)(程序跑 0-1 背包 dp[20] = 44 验证)。
2025 年 · 第 14 题¶
题目
斐波那契数列的定义为 \(F(0)=0\),\(F(1)=1\),\(F(n) = F(n-1) + F(n-2)\)。使用朴素递归方法计算 \(F(n)\) 的时间复杂度是指数级的。而使用动态规划(或迭代)方法的时间复杂度是线性的。造成这种巨大差异的根本原因是?
- A. 递归函数调用栈开销过大
- B. 操作系统对递归深度有限制
- C. 朴素递归中存在大量的重叠子问题未被重复利用
- D. 动态规划使用了更少的数据存储空间
答案与解析
答案:C
重叠子问题重复计算是指数爆炸的根源;栈开销只是常数级放大。
易错小结¶
- 手算递归展开成树、先叶后根回代;ack 类先找小规律(\(ack(2,n) = 2n+3\));
- 递归层数过多 = 栈溢出(2021 原题),与队列/堆无关;
- 朴素递归 vs DP 的差距根源 = 重叠子问题(2025 原题),不是栈开销;
- \(T(n)=2T(n/2)+O(n^2)\) = \(O(n^2)\)(几何收敛顶层支配),\(+O(n)\) 才是 \(O(n\log n)\)——别背串;
- 背包题小规模可枚举验证:2025 真题 4 件恰满 44;
- 质数模下 \(a^{p-1} \equiv 1\)(费马小定理)速算 \(5^{22} \bmod 23\)。