当前位置: 首页 > news >正文

第4章 递推法

4.1 递推法概述

设计思想
递推法(Recurrence Method)通过已知的初始条件和递推关系,逐步推导出问题的最终结果,常用于序列计算和分阶段问题求解。


示例:猴子和桃子问题

题目描述
猴子每天吃掉剩余桃子的一半再多吃一个,第 10 天只剩 1 个桃子,问最初有多少个桃子?

思路

  • 设 a[n] 为第 n 天结束后剩余的桃子数;

  • 已知 a[10] = 1;

  • 从后向前有递推关系:

    a[n] = (a[n+1] + 1) × 2
    

代码实现

// MonkeyPeach:计算第1天最初的桃子数
int MonkeyPeach(int days) {// days 表示总共天数,本题为 10int peaches = 1;           // 从第 days-1 天开始倒推到第 1 天for (int day = days - 1; day >= 1; day--) {// 根据递推关系 a[n] = (a[n+1] + 1) * 2// peaches 此时保存 a[n+1],更新后即为 a[n]peaches = (peaches + 1) * 2;}return peaches;            
}

整体解释
我们先假设最后一天剩余 1 个桃子,然后按照“后一天的桃子数加 1 再乘 2”这个公式,依次向前推算,每一步都将当前天的剩余数计算出来,循环结束后 peaches 即为第 1 天最初的桃子数。


4.2 数学序列中的递推法

4.2.1 斐波那契数列

题目描述
兔子繁殖问题:第 1、2 个月各有 1 对兔子,从第 3 个月起每月新增的兔子对数等于前两个月兔子对数之和,求第 n 个月的兔子对数。

递推关系

f(1) = 1,  f(2) = 1
f(n) = f(n-1) + f(n-2),  n ≥ 3

代码实现

// Fibonacci:计算第 n 个月的兔子对数
int Fibonacci(int n) {// 前两个月的兔子对数均为 1if (n <= 2) return 1;int prev = 1;   // f(i-2)int curr = 1;   // f(i-1)int next;       // f(i)// 从第 3 个月开始循环for (int i = 3; i <= n; i++) {next = prev + curr;   // 应用 f(i) = f(i-1) + f(i-2)prev = curr;          // 将 f(i-1) 赋值给 prevcurr = next;          // 将 f(i) 赋值给 curr}return curr;  // 返回 f(n)
}

整体解释
我们只记录前两项 prevcurr,每次计算新的一项 next,然后滚动更新这两个变量,最后 curr 存储的就是第 n 个月的兔子对数。此方法空间复杂度 O(1),时间复杂度 O(n)。


4.2.2 卡塔兰数

题目描述
凸 n 边形划分成三角形的不同方式数,第 n 个卡塔兰数 C(n) 满足:

C(0) = 1
C(n) = ∑_{i=0}^{n-1} C(i) × C(n-1-i),  n ≥ 1

代码实现

// Catalan:计算第 n 个卡塔兰数
int Catalan(int n) {// 分配数组存储 0...n 的 C 值int C[n+1];C[0] = 1;  // C(0) = 1// 依次计算 C(1) 到 C(n)for (int i = 1; i <= n; i++) {C[i] = 0;// 按定义累加for (int k = 0; k < i; k++) {C[i] += C[k] * C[i - 1 - k];}}return C[n];
}

整体解释
使用数组 C[] 自底向上存储卡塔兰数,通过两层循环,外层决定要计算的下标 i,内层按公式累加前面各项乘积,最终 C[n] 即为答案。时间复杂度 O(n²),空间复杂度 O(n)。


4.3 组合问题中的递推法

4.3.1 错排问题

题目描述
有 n 封信和 n 个信封,要求没有信件放入正确的信封,求错排方案数 D(n),递推关系:

D(1) = 0
D(2) = 1
D(n) = (n - 1) × (D(n-1) + D(n-2)),  n ≥ 3

代码实现

// Derangement:计算错排数 D(n)
int Derangement(int n) {if (n == 1) return 0;  // D(1) = 0if (n == 2) return 1;  // D(2) = 1int dn_2 = 0;  // D(n-2)int dn_1 = 1;  // D(n-1)int dn;        // D(n)// 从 n=3 开始迭代for (int i = 3; i <= n; i++) {dn = (i - 1) * (dn_1 + dn_2);dn_2 = dn_1;  // 滚动更新 D(n-2)dn_1 = dn;    // 滚动更新 D(n-1)}return dn;      // 返回 D(n)
}

整体解释
只需保留前两项 dn_2dn_1,用递推公式计算当前项 dn,再向后滚动即可,空间复杂度 O(1),时间复杂度 O(n)。


4.3.2 旋转万花筒

题目描述
起始有 4 个闪光点,每次旋转在每个分支末端增加 2 个闪光点,问 n 次旋转后总闪光点数。

代码实现

// Kale:计算旋转 n 次后的闪光点总数
int Kale(int n) {int lamps = 4;      // 初始闪光点数int addLamp = 2;    // 每个分支基础新增数for (int i = 1; i <= n; i++) {// 本次新增数量是上次的两倍addLamp *= 2;// 累加到总闪光点数lamps += addLamp;}return lamps;
}

整体解释
变量 addLamp 跟踪每次新增的闪光点数,每次翻倍后累加到 lamps,循环结束后 lamps 为旋转 n 次的总数。时间复杂度 O(n)。


4.4 拓展与演练

4.4.1 整数拆分(2 的幂次划分)

题目描述
将正整数 n 拆分为若干 2 的幂次之和,求拆分方案数 d(n),递推关系:

d(1) = 1  
d(2) = 2  
若 i 为奇数: d(i) = d(i - 1)  
否则:      d(i) = d(i - 1) + d(i / 2)

代码实现

// PowerSplit:计算 2 的幂次拆分方案数
int PowerSplit(int n) {int d[n + 1];   // 存储从 1 到 n 的方案数d[1] = 1;       // d(1) = 1d[2] = 2;       // d(2) = 2for (int i = 3; i <= n; i++) {if (i % 2 != 0) {// 奇数只能继承前一个的方案d[i] = d[i - 1];} else {// 偶数可在继承前一个方案基础上,加上包含 i/2 的拆分d[i] = d[i - 1] + d[i / 2];}}return d[n];
}

整体解释
用一维数组 d[] 自底向上记录每个 i 的方案数,遇到奇数直接复制,偶数则累加前一项和 i/2 的方案即可。时间复杂度 O(n),空间 O(n)。


4.4.2 捕鱼问题

题目描述
5 人轮流捕鱼,每人将看到的鱼分成 5 份,丢弃 1 条并带走 1 份,其余留给下一人,直到最后一人也按此规则操作,求最少的初始鱼数及每人捕鱼时看到的鱼数。

思路
从最小可能的初始鱼数开始尝试,依次验证每个人都能整除且满足规则。

代码实现

// GetFish:计算最少的初始鱼数
int GetFish(int nPeople) {int fish[5];         // fish[i] 记录第 i+1 个人见到的鱼数fish[0] = 1;         // 从最少 1 条开始尝试while (1) {fish[0]++;       // 逐次增加初始鱼数bool valid = true;// 验证每个人是否都能按规则操作for (int i = 1; i < nPeople; i++) {// (看见数 - 1) 必须能被 5 整除if ((fish[i - 1] - 1) % 5 != 0) {valid = false;break;}// 每人带走1份,留给下一个的人 = (fish[i-1]-1)/5*4fish[i] = (fish[i - 1] - 1) / 5 * 4;}if (valid) break;  // 全部满足则结束循环}return fish[0];
}

整体解释
数组 fish[] 存储每个人见到的鱼数,从第一个人开始试探最小初始值,每次尝试后向下验证,若所有人都满足“(见到数-1) 能被 5 整除”,则该初始值即为答案。时间复杂度较高,但能够找到最小解。

相关文章:

  • 1688拍立淘搜索相似商品API接口概述,json数据示例参考
  • 【编译原理】第五章 自下而上语法分析
  • HTML基础2-空元素,元素属性与页面的结构
  • 第35周Zookkeeper+Dubbo Dubbo
  • 力扣热题100之回文链表
  • CPU的基本认识
  • 企业数字化转型第二课:接受不完美(1/2)
  • 2025最新出版 Microsoft Project由入门到精通(二)
  • springboot国家化多语言实现
  • Python中的global与nonlocal关键字详解
  • AV1中的维纳滤波器
  • 数据管道的解耦艺术:Dagster I/O管理器实现存储与逻辑分离
  • Android开发-文本显示
  • 数通HCIE的通过率怎么样?
  • Linux 内核学习(7) --- 字符设备驱动
  • 蓝牙L2CAP协议概述
  • 前端日常 · 移动端网页调试
  • C——函数递归
  • Vue 项目中二维码生成功能全解析
  • 数智管理学(八)
  • 两部门发布山洪灾害气象预警:北京西部、河北西部等局地山洪可能性较大
  • 大四本科生已发14篇SCI论文?学校工作人员:已记录汇报
  • 国家发改委:目前有的核电项目民间资本参股比例已经达到20%
  • 观察|22项达全球最优,世行为上海营商环境“盖章”
  • 碧桂园服务:拟向杨惠妍全资持有的公司提供10亿元贷款,借款将转借给碧桂园用作保交楼
  • 潘功胜:央行将创设科技创新债券风险分担工具