跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

动态规划在斐波那契数列中的应用与优化

通过动态规划方法解决了四个经典算法问题:第 N 个泰波那契数、三步问题、最小花费爬楼梯和解码方法。详细阐述了状态定义、转移方程、初始化及填表顺序,并展示了空间优化技巧。代码使用 C++ 实现,涵盖递归转迭代及滚动数组优化,适用于算法学习与面试准备。

星落发布于 2026/3/30更新于 2026/10/24111 浏览
动态规划在斐波那契数列中的应用与优化

前言

斐波那契数列是数学领域中一个经典的问题,在计算机科学中也有广泛的应用。从简单的递归算法到优化的动态规划方法,斐波那契数列的求解体现了算法设计和性能优化的精髓。本文将以动态规划为核心,系统地探讨如何高效地计算斐波那契数列,分析不同方法的时间与空间复杂度,并展示动态规划的强大之处。

一、1137. 第 N 个泰波那契数

题目链接:https://leetcode.cn/problems/n-th-tribonacci-number/description/

1. 题目解析

Tribonacci 数列是一个递归数列,类似于斐波那契数列,但它的递推公式是:

  • 递推公式:T(n) = T(n-1) + T(n-2) + T(n-3),对于 n >= 3;
  • 初始条件:
    • T(0) = 0
    • T(1) = 1
    • T(2) = 1

需要实现一个函数 tribonacci(int n),返回第 n 个 Tribonacci 数。

2. 讲解算法原理
状态表示

设 dp[i] 表示第 i 个 Tribonacci 数,即前 i 个数的第三阶斐波那契数列。换句话说,dp[i] 是定义如下的递推关系:

  • dp[i] = dp[i-1] + dp[i-2] + dp[i-3],对于 i >= 3。
状态转移方程

根据题目描述,Tribonacci 数列满足:

  • dp[0] = 0
  • dp[1] = 1
  • dp[2] = 1
  • dp[i] = dp[i-1] + dp[i-2] + dp[i-3],对于 i >= 3
初始化

初始化初始条件,保证在填写表格的时候不会越界:

  • dp[0] = 0
  • dp[1] = 1
  • dp[2] = 1
填表顺序

从 i = 3 开始递推填表,因为计算 dp[i] 时,需要依赖 dp[i-1], dp[i-2], 和 dp[i-3] 的值,这些值在计算当前状态时必须已知。因此:

  • 填写顺序是从 i = 3 开始递增。
返回值

最后返回 dp[n],即第 n 个 Tribonacci 数。

3. 编写代码
class Solution {
public:
      {
        
         (n == )  ;
         (n ==  || n == )  ;
        
        
        ;
        dp[] = , dp[] = dp[] = ;
        
        
         ( i = ; i <= n; i++) {
            
            dp[i] = dp[i - ] + dp[i - ] + dp[i - ];
        }
        
        
         dp[n];
    }
};
int
tribonacci
(int n)
// 3. 初始化(初始条件)
if
0
return
0
if
1
2
return
1
// 1. 状态表示:dp[i] 表示第 i 个 Tribonacci 数
vector<int> dp(n + 1)
0
0
1
2
1
// 4. 填表顺序:从 i = 3 到 n
for
int
3
// 2. 状态转移方程
1
2
3
// 5. 返回值:第 n 个 Tribonacci 数
return
4. 空间优化
class Solution {
public:
    int tribonacci(int n) {
        if (n == 0) return 0;
        if (n == 1 || n == 2) return 1;
        
        int a = 0, b = 1, c = 1, d = 0; // 初始状态
        for (int i = 3; i <= n; i++) {
            d = a + b + c; // 当前状态依赖于前三个状态
            a = b;         // 滚动更新
            b = c;
            c = d;
        }
        return d; // 返回第 n 项
    }
};
  1. 状态转移方程:
    • T(n) = T(n-1) + T(n-2) + T(n-3)。
    • 每次计算当前值 T(n) 只需要 T(n-1)、T(n-2) 和 T(n-3)。
  2. 滚动更新变量:
    • 变量 a、b、c 分别存储 T(n-3)、T(n-2) 和 T(n-1)。
  3. 减少空间复杂度:
    • 原本需要一个大小为 n 的数组来存储所有状态。
    • 滚动数组通过动态更新变量,将空间复杂度从 O(n) 优化为 O(1)。

每次计算 d = a + b + c 后,将 a、b、c 滚动更新为下一轮所需的值:

a = b; // T(n-2) 成为 T(n-3)
b = c; // T(n-1) 成为 T(n-2)
c = d; // T(n) 成为 T(n-1)

二、面试题 08.01. 三步问题

题目链接:https://leetcode.cn/problems/three-steps-problem-lcci/

1. 题目解析

这道题的目标是计算可以通过步数 1、2 和 3 从 0 到达台阶 n 的所有不同方法的总数。 每次可以选择迈 1 步、2 步或 3 步,并且答案需要对 10^9 + 7 取模。

2. 讲解算法原理
状态表示

定义一个数组 dp[i],表示到达第 i 个台阶的方法总数。

  • dp[i] 包括从 i−1、i−2、或 i−3 的台阶迈一步到达的所有方案数。
状态转移方程

状态转移公式为:

dp[i] = (dp[i-1] + dp[i-2] + dp[i-3]) % MOD

这里的取模操作是为了防止结果过大,确保结果保持在 10^9 + 7 的范围内。

初始化

根据题目条件:

  • 如果 n = 1,只有 1 种方法,初始化 dp[1] = 1;
  • 如果 n = 2,有 2 种方法,初始化 dp[2] = 2;
  • 如果 n = 3,有 4 种方法,初始化 dp[3] = 4。
填表顺序

从小到大依次计算 dp[4] 到 dp[n],通过累加前 3 项得到结果。

返回值

最终返回 dp[n] 即可。

3. 编写代码
class Solution {
public:
    int waysToStep(int n) {
        if (n == 1 || n == 2) return n;
        if (n == 3) return 4;
        
        const int MOD = 1e9 + 7;
        vector<int> dp(n + 1);
        dp[1] = 1, dp[2] = 2, dp[3] = 4;
        
        for (int i = 4; i <= n; i++) {
            dp[i] = ((dp[i - 1] + dp[i - 2]) % MOD + dp[i - 3]) % MOD;
        }
        return dp[n];
    }
};

三、746. 使用最小花费爬楼梯

题目链接:https://leetcode.cn/problems/min-cost-climbing-stairs/description/

1. 题目解析

这道题的目标是计算从数组开头或第二个元素出发,到达数组末尾所需的最小花费。 每次可以迈 1 步或 2 步,花费由数组 cost 决定,其中每个位置的值代表站在对应台阶上的代价。

2. 讲解算法原理
状态表示

定义一个数组 dp[i]:

  • dp[i] 表示到达台阶 i 所需的最小花费。
状态转移方程

我们可以从前一层 (i−1) 或前两层 (i−2) 迈步到 i,取最小值作为最优解:

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

初始化

因为可以从第 0 层或第 1 层开始:

  • dp[0] = 0:从起点出发,不需要额外代价。
  • dp[1] = 0:从起点开始直接跳到第 1 层,也没有代价。
填表顺序

从小到大计算 dp[i],直至 dp[n],其中 n 是楼梯台阶数。

返回值

最终返回 dp[n],即为到达顶部所需的最小花费。

3. 编写代码

1. 解法一:自底向上填表

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n + 1);
        for (int i = 2; i <= n; i++) {
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[n];
    }
};

2. 解法二:从顶层反推

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n);
        dp[n - 1] = cost[n - 1];
        dp[n - 2] = cost[n - 2];
        
        for (int i = n - 3; i >= 0; i--) {
            dp[i] = min(dp[i + 1] + cost[i], dp[i + 2] + cost[i]);
        }
        return min(dp[0], dp[1]);
    }
};

四、91. 解码方法

题目链接:https://leetcode.cn/problems/decode-ways/description/

1. 题目解析

本题要求解码一个只包含数字的字符串 s,其中每个数字代表字母表中的字母(1 对应 'A',2 对应 'B',…,26 对应 'Z')。 我们的任务是计算出所有可能的解码方案的数量。

2. 讲解算法原理
状态表示

定义一个数组 dp,其中 dp[i] 表示字符串 s 的前 i 个字符能够解码的方式总数。

状态转移方程

两个字符解码: 如果前两个字符组成的数值在 [10, 26] 范围内,那么它们可以合并解码成一个字母。例如,s[i-2] 和 s[i-1] 组合形成一个数,如果这个数在 [10, 26] 内,则可以由 dp[i-2] 转移:

int t = (s[i-2]-'0')*10+(s[i-1]-'0');
if(t >= 10 && t <= 26) dp[i] += dp[i-2];

单个字符解码: 如果当前位置的字符 s[i-1] 是有效的(即不等于 '0'),它可以独立解码为一个字母。 因此,dp[i] 可以由 dp[i-1] 转移而来:

if(s[i-1]!='0') dp[i] += dp[i-1];
初始化
  • dp[0] = 1:表示空字符串有 1 种解码方法。
  • dp[1] = s[0] != '0':如果第一个字符不为 '0',则有 1 种解码方法,否则没有解码方法。
填表顺序

从 i = 2 开始填表,一直到 i = n,逐步计算每个 dp[i] 的值。

返回值

最终返回 dp[n],即为整个字符串的解码方法数量。

3. 编写代码
class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        vector<int> dp(n + 1);
        dp[0] = 1; // 保证后面的填表是正确的
        dp[1] = s[0] != '0'; // 第一个字符不能是 '0'
        
        for (int i = 2; i <= n; i++) {
            // 如果当前字符不是 '0',可以单独解码
            if (s[i - 1] != '0') dp[i] += dp[i - 1];
            
            // 如果当前和前一个字符组成的数字在 10 到 26 之间,也可以解码
            int t = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');
            if (t >= 10 && t <= 26) dp[i] += dp[i - 2];
        }
        return dp[n];
    }
};

目录

  1. 前言
  2. 一、1137. 第 N 个泰波那契数
  3. 1. 题目解析
  4. 2. 讲解算法原理
  5. 状态表示
  6. 状态转移方程
  7. 初始化
  8. 填表顺序
  9. 返回值
  10. 3. 编写代码
  11. 4. 空间优化
  12. 二、面试题 08.01. 三步问题
  13. 1. 题目解析
  14. 2. 讲解算法原理
  15. 状态表示
  16. 状态转移方程
  17. 初始化
  18. 填表顺序
  19. 返回值
  20. 3. 编写代码
  21. 三、746. 使用最小花费爬楼梯
  22. 1. 题目解析
  23. 2. 讲解算法原理
  24. 状态表示
  25. 状态转移方程
  26. 初始化
  27. 填表顺序
  28. 返回值
  29. 3. 编写代码
  30. 四、91. 解码方法
  31. 1. 题目解析
  32. 2. 讲解算法原理
  33. 状态表示
  34. 状态转移方程
  35. 初始化
  36. 填表顺序
  37. 返回值
  38. 3. 编写代码

更多推荐文章

查看全部
  • AI 在医疗领域的十大应用场景解析
  • FPGA 读写 DDR4 (一) MIG IP 核控制信号
  • Python 实现十大经典排序算法详解:原理、代码与复杂度分析
  • WebAssembly 逆向分析:反编译 Wasm 二进制并修改游戏数据
  • 6 款 AI 辅助论文写作工具概览
  • Trae IDE 深度指南:模型管理与实战技巧
  • C++ 红黑树详解与实现
  • 无需第三方工具直接下载 macOS 官方镜像指南
  • Web-Rooter:基于 IR + Lint 模式的 AI Agent 联网工具
  • ClawX:OpenClaw 桌面版 AI 助手部署指南
  • 大模型企业分化加剧,算力市场正式转入买方时代
  • C++11 右值引用与移动语义详解:从性能瓶颈到零拷贝优化
  • 数据结构:图的存储结构与核心算法解析
  • Flutter 集成 BIP340 实现鸿蒙 Schnorr 签名实战
  • Whisper-large-v3 多任务并行:同一服务支持转录/翻译/摘要
  • 飞算JavaAI实战:在线图书借阅平台设计与实现
  • MongoDB 详细安装与配置指南(Windows / Linux / macOS)
  • Qwen-Image-Lightning 极速图像生成技术解析
  • 基于 OpenClaw 和飞书开放平台实现 AI 新闻推送机器人
  • 前端无障碍性实践:确保网站对所有人可用

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online