斐波那契数列 - 动态规划实现 详解笔记

1. 📚 问题描述

斐波那契数列(Fibonacci Sequence)是一个经典的数学序列,定义如下:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n-1) + F(n-2),其中 n ≥ 2

数列展示0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...


2. 🎯 动态规划思想

与递归相比,动态规划通过存储子问题的解避免重复计算,核心要素包括:

要素 说明 在本题中的应用
🔑 最优子结构 问题的最优解包含子问题的最优解 F(n) 的解依赖于 F(n-1)F(n-2)
💾 记忆化 将已计算的结果存储起来避免重复计算 数组或变量存储中间结果
自底向上 从基础情况开始逐步构建到目标问题 F(0)F(1) 开始计算到 F(n)
🚀 空间优化 根据状态依赖关系降低空间复杂度 O(n) 优化到 O(1)

3. 📊 算法对比

方法 时间复杂度 空间复杂度 优缺点
递归(暴力) O(2ⁿ) O(n) ❌ 重复计算极多,效率极低,指数级爆炸
动态规划(数组) O(n) O(n) ✅ 高效,易理解,保留所有中间结果
动态规划(降维) O(n) O(1) ✅ 最优解,空间效率最高,仅保留必要状态
矩阵快速幂 O(log n) O(1) ⚡ 最快但实现复杂,适合超大n,理论最优

4. 📝 示例演示:计算 F(6)

4.1 方法1:使用数组(O(n)空间)

java 复制代码
初始化数组:arr[0]=0, arr[1]=1

i=2: arr[2] = arr[1] + arr[0] = 1 + 0 = 1
i=3: arr[3] = arr[2] + arr[1] = 1 + 1 = 2
i=4: arr[4] = arr[3] + arr[2] = 2 + 1 = 3
i=5: arr[5] = arr[4] + arr[3] = 3 + 2 = 5
i=6: arr[6] = arr[5] + arr[4] = 5 + 3 = 8

结果:F(6) = 8
数组状态:[0, 1, 1, 2, 3, 5, 8]

4.2 方法2:降维优化(O(1)空间)

java 复制代码
初始化:i=0 (F₀), j=1 (F₁)

k=2: c = 0+1=1,  更新 i=1, j=1  (计算F₂)
k=3: c = 1+1=2,  更新 i=1, j=2  (计算F₃)
k=4: c = 1+2=3,  更新 i=2, j=3  (计算F₄)
k=5: c = 2+3=5,  更新 i=3, j=5  (计算F₅)
k=6: c = 3+5=8,  更新 i=5, j=8  (计算F₆)

结果:F(6) = 8
内存占用:仅需3个int变量!

5. 🌟 应用场景

  • 🪜 爬楼梯问题:每次爬1或2阶,求到达n阶的方法数
  • 🐰 兔子繁殖问题:经典的斐波那契起源问题
  • 🌻 黄金分割:相邻两项比值趋近于黄金分割比 φ ≈ 1.618
  • 🎨 艺术设计:斐波那契螺旋线在自然界和艺术中广泛存在
  • 📊 算法优化:学习动态规划的经典入门案例
  • 🍃 植物生长:树叶的排列、花瓣数量等自然现象
  • 📈 金融分析:技术分析中的斐波那契回调线

6. 💻 代码实现详解

6.1 方法1:动态规划 - 使用数组存储

java 复制代码
/**
 * 使用数组实现斐波那契数列
 * 时间复杂度:O(n)
 * 空间复杂度:O(n)
 */
public static int fibonacci(int n) {
    // 创建数组存储所有斐波那契数
    int[] arr = new int[n + 1];
    
    // 初始化前两项
    arr[0] = 0;
    arr[1] = 1;

    // 处理边界情况
    if (n < 2) {
        return arr[n];
    }

    // 自底向上计算每一项
    for (int i = 2; i <= n; i++) {
        arr[i] = arr[i - 1] + arr[i - 2];  // 状态转移方程
    }

    return arr[n];
}

优点

  • 逻辑清晰,易于理解
  • 保留了所有中间结果,便于调试
  • 如果需要多次查询不同的 F(i),只需计算一次

缺点

  • 空间复杂度较高,对于超大 n 可能内存不足
  • 计算单个 F(n) 时存储了大量不必要的中间结果

6.2 方法2:动态规划 - 空间优化(降维)

java 复制代码
/**
 * 使用降维优化实现斐波那契数列
 * 时间复杂度:O(n)
 * 空间复杂度:O(1) - 只使用常数个变量
 */
public static int fibonacciJW(int n) {
    // 处理边界情况:F(0) = 0
    if (n == 0) {
        return 0;
    }

    // 处理边界情况:F(1) = 1
    if (n == 1) {
        return 1;
    }

    // 初始化两个变量:i表示F(k-2),j表示F(k-1)
    int i = 0;  // F(0)
    int j = 1;  // F(1)
    
    // 从第2项开始滚动计算到第n项
    for (int k = 2; k <= n; k++) {
        int c = i + j;  // 计算当前项 F(k) = F(k-1) + F(k-2)
        i = j;          // 向前滚动:F(k-2) = 旧的F(k-1)
        j = c;          // 向前滚动:F(k-1) = 新计算的F(k)
    }

    // 循环结束后,j 就是 F(n)
    return j;
}

核心思想

观察到计算 F(n) 只需要 F(n-1)F(n-2),不需要保存所有历史值。使用两个变量滚动更新 ,将空间复杂度从 O(n) 降到 O(1)

变量含义

  • i:表示 F(k-2),前前一项
  • j:表示 F(k-1),前一项
  • c:表示 F(k),当前项 = i + j

⚠️ 注意事项

  • 变量更新顺序很重要:先算 c,再更新 ij
  • 循环结束后,j 存储的就是 F(n)
  • 如果需要保留所有中间结果,应使用方法1

7. 🧪 测试方法

java 复制代码
public static void main(String[] args) {
    // 测试用例:计算 F(21)
    // 期望输出:10946(两种方法结果相同)
    System.out.println(fibonacci(21));      // 使用数组方法
    System.out.println(fibonacciJW(21));    // 使用降维方法
}

测试结果验证

复制代码
10946
10946

F(21) 完整数列
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946


8. 📈 复杂度对比总结

实现方式 时间复杂度 空间复杂度 推荐场景
数组存储 O(n) O(n) 需要多次查询不同F(i),或需要调试中间状态
降维优化 O(n) O(1) 仅需计算单个F(n),追求极致空间效率
矩阵快速幂 O(log n) O(1) n极大(如10⁹)时,对时间要求苛刻的场景

9. 🔧 扩展思考

9.1 如何选择实现方式?

  • n ≤ 10⁶ :优先使用降维优化方法,简单高效
  • 需要多次查询 :使用数组方法,空间换时间
  • n极大(10⁹级别) :考虑矩阵快速幂通项公式
  • 教学演示 :从递归数组DP降维逐步演进

9.2 从斐波那契到动态规划

斐波那契是学习DP的最佳入门案例

  1. ✅ 有明确的状态转移方程
  2. ✅ 具备最优子结构特性
  3. ✅ 存在重叠子问题(递归会重复计算)
  4. ✅ 容易进行空间优化

9.3 性能测试建议

java 复制代码
// 测试大数值性能(注意递归会栈溢出)
long start = System.currentTimeMillis();
System.out.println(fibonacciJW(1000000)); // 计算F(100万)
long end = System.currentTimeMillis();
System.out.println("耗时: " + (end - start) + "ms"); // 约10-20ms

10. ✅ 总结

10.1 核心要点

  1. 状态转移F(n) = F(n-1) + F(n-2) 是动态规划的精髓
  2. 自底向上:从已知到未知,避免递归的重复计算
  3. 空间优化 :观察状态依赖,将 O(n) 空间降至 O(1)
  4. 边界处理F(0)=0F(1)=1 是计算基础

10.2 学习建议

  • 初学者:先掌握数组方法,理解DP流程
  • 进阶:挑战降维优化,理解状态压缩思想
  • 高阶:尝试实现矩阵快速幂,探索O(log n)解法
  • 实践:用斐波那契解决爬楼梯、兔子繁殖等实际问题

10.3 代码优势

  • 清晰注释:每一步都有详细说明
  • 两种实现:对比学习,理解空间优化
  • 边界完备 :处理了 n=0n=1 的特殊情况
  • 教学友好:适合作为动态规划的入门案例

11. 📚 相关题目推荐

题目 难度 关联度 核心思想
LeetCode 70 - 爬楼梯 Easy ★★★★★ 斐波那契的直接应用
LeetCode 509 - 斐波那契数 Easy ★★★★★ 本题的官方版本
LeetCode 746 - 使用最小花费爬楼梯 Easy ★★★★☆ 变体DP问题
LeetCode 1137 - 第N个泰波那契数 Easy ★★★☆☆ 三项递推,思路类似
相关推荐
断剑zou天涯41 分钟前
【算法笔记】从暴力递归到动态规划(三)
java·算法·动态规划
RQ_ghylls41 分钟前
2.excel每3行计算一个均值,将高于均值的单元格设置背景红色
算法·均值算法·word·excel
断剑zou天涯44 分钟前
【算法笔记】从暴力递归到动态规划(一)
java·算法·动态规划
小毛驴8501 小时前
软件设计模式-代理模式
设计模式·系统安全·代理模式
不爱编程爱睡觉1 小时前
代码随想录算法训练营第二十八天 | 动态规划算法基础、 LeetCode509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯
算法·leetcode·动态规划·代码随想录
Ace_31750887761 小时前
微店平台关键字搜索接口深度解析:从 Token 动态生成到多维度数据挖掘
java·前端·javascript
yyt3630458411 小时前
Maven 命令构建成功但 IDEA 构建失败原因解析
java·maven·intellij-idea
krafft1 小时前
从零入门 Spring AI,详细拆解 ChatClient 调用流程和 Advisor 底层原理,小白可入!
java·spring·ai
j***82701 小时前
Spring 中集成Hibernate
java·spring·hibernate