斐波那契数列的优化方案
题目描述
根据之前的斐波那契数列简单版,可以看出,我们使用递归只能在一秒内算出第 \(46\) 级台阶的走法
这是因为递归是一种极其低效的算法,每次都会重复计算很多数据,它既要把问题分解递到更小的问题上去,还要把答案从小的问题归回到最后的问题上
那么,我们可以思考这样一个问题,能不能直接从小问题入手,慢慢推导到最终的问题上面去呢?
引入这次的核心内容:递推
对于这道题而言,我们可以知道
fib[i] = (fib[i - 1] + fib[i - 2]) % 1000;//注意题目要求取模
而且我们的fib[1]
和fib[2]
都为 \(1\)
所有我们循环从 \(3\) 开始,把题目要求的最大 1e6
的数据都提前计算出来,存在数组中
每次询问第 \(a\) 级台阶时,直接通过下标输出已经计算好的答案
const int N = 1e6; int fib[N+10];//多开10个空间 void pre(void) { fib[1] = 1, fib[2] = 1;//初始化递推起点 for (int i = 3; i <= N; i++)//从3开始递推 fib[i] = (fib[i - 1] + fib[i - 2]) % 1000;//递推公式 }
上述的代码,我们定义了一个pre
函数,目的是提前预处理出所有可能用到的数据
cin >> n; while (n--) { int a; cin >> a; cout << fib[a] << endl;//直接从已有的答案中输出结果 }
以上就是一道简单的递推题目,时间复杂度为\(O(n)\),远远比我们递归(无优化)的\(O(2^n)\)优秀得多
当然,我们也可以对我们的递归进行优化操作——记忆化搜索
在计算fib[i] = fib[i - 1] + fib[i - 2]
时,递归到下一层(可以看作有左右两条路)
程序会先走左边的路,计算 fib[i - 1] = fib[i - 2] + fib [i - 3]
,直到这条路走到底
然后归回这条路的答案到上一次,再走上一层的右边的路
不难发现,我们在走左边的路的时候,已经计算过fib[i-2]
的值了,所以在走右边的路的时候,我们就可以不用重复计算它的值,直接拿算出来的数据直接使用即可
所以需要在递归时记录我们计算过的值
int find(int x) { if (fib[x]) return fib[x]; return fib[x] = (find(x - 1) + find(x - 2)) % 1000;//保存结果 } cout << find(a) << endl;
但是,由于我们递归的深度可以达到1e6
,很多编译器会出现栈溢出的问题,并且占用的内存极大,非必要不采用
(上为记忆化递归,下为递推)
也可以进行尾递归的优化操作
每次递归时,把当前的运算结果(或路径)放在参数里传给下层函数,即栈的覆盖
其中,u
为当前层的值,v
为上一层的值
int tailfind(int n, int u = 1, int v = 1) {//如果没有传入u,v的值,就默认为1,1 if (n == 1) return u % 1000; if (n == 2) return v % 1000; return tailfind(n - 1, v, (u + v) % 1000); } cout << tailfind(a) << endl;
可以看见,内存的占用大大降低了,但是仍然不如我们最优的递推操作
完整AC代码:
递推:
#include <iostream> using namespace std; const int N = 1e6; int fib[N + 10]; void pre(void) { fib[1] = 1, fib[2] = 1; for (int i = 3; i <= N; i++) fib[i] = (fib[i - 1] + fib[i - 2]) % 1000; } int main() { int n; cin >> n; pre(); while (n--) { int a; cin >> a; cout << fib[a] << endl; } return 0; }
记忆化递归:
#include <iostream> using namespace std; const int N = 1e6; int fib[N + 10]; int find(int x) { if (fib[x]) return fib[x]; return fib[x] = (find(x - 1) + find(x - 2)) % 1000; } int main() { int n; cin >> n; fib[1] = fib[2] = 1; while (n--) { int a; cin >> a; cout << find(a) << endl; } return 0; }
尾递归:
#include <iostream> using namespace std; const int N = 1e6; int fib[N + 10]; int tailfind(int n, int u = 1, int v = 1) { if (n == 1) return u % 1000; if (n == 2) return v % 1000; return tailfind(n - 1, v, (u + v) % 1000); } int main() { int n; cin >> n; fib[1] = fib[2] = 1; while (n--) { int a; cin >> a; cout << tailfind(a) << endl; } return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程