算法
斐波那契数列的逐步优化
问题本身不难,主要体会优化的过程。
什么是斐波那契数列:
F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
满足这个公式的数列就称为斐波那契数列。
最简单的实现方式——递归
我们容易想到的斐波那契数列的求法就是递归,当然递归也是最简单的解决办法,那么先从递归开始看。
递归实现斐波那契数列的代码如下:
int fid(int n)
{
if(n == 1 || n == 2) return 1;
return fid(n-1) + fid(n-2);
}
假设上述代码N传20,可以画出递归树如下:
可以看出有很多重复计算,再来计算一下它的时间复杂度,有节点的个数很明显是2n-1个在指数级别,一个节点对应一次运算所以是O(1),所以它的时间复杂度就是O(2n)。
优化思路——去重复
我们可以做一个“备忘录”将计算过的节点记录,当下次访问计算过的节点时直接拿来用。
加了“备忘录”的代码如下:
int fid(int n)
{
if(n == 1 || n == 2) return 1;
vector<int> data(n+1, 0); // 第一位空出来不用
return fid(n, data);
}
int fid(int n, vector<int>& data)
{
if(n == 1 || n == 2) return 1;
if(data[n] != 0) return data[n]; // 先判断,没有计算过再计算
else data[n] = fid(n-1, data) + fid(n-2, data);
return data[n];
}
加了“备忘录”递归图如下:
如此一来就省略了很多次重复的运算,因为“备忘录”的作用,将原始的递归树进行了裁剪。
再看此时的时间复杂度:有节点个数与n是成正比的所以子问题个数为O(n),解决一个子问题没有循环所以是O(1),所以此时的时间复杂度为O(n)。
将指数级的时间复杂度降到了线性级别。但是递归总要开辟栈空间,同时我们还新增了一个“备忘录”大小为n+1 此时的空间复杂度是O(n2)。
所以我们可以继续优化:
上述本质上是自顶向下的解决问题,那么我们可以反过来自底向上的推导。
既然递归是自顶向下并且开辟额外的栈空间那么就避免递归。
使用循环实现代码如下:
int fid(int n)
{
if(n == 1 || n == 2) return 1;
vector<int> data(n+1, 0);
data[1] = 1;
data[2] = 1;
for(int i = 3; i <= n; ++i){
data[i] = data[i-1] + data[i-2];
}
return data[n];
}
这样以来时间和空间复杂度都为O(n)
接下来就是最后一步优化了:空间复杂度优化为O(1)
代码如下:
int fid(int n)
{
if(n == 1 || n == 2) return 1;
int first = 1;
int second = 1;
int res = 0;
for(int i = 3; i <= n; ++i){
res = first + second;
first = second;
second = res;
}
return res;
}
面试题
宏中#和##的区别
“#”表示宏替换为字符串
“##”表示拼接为字符串
使用宏函数演示代码如下:
#define FUN(x) #x // 将x替换为"x"字符串
#define TFUN(x+y) x##y // 将x和y拼接为字符串,不管调用宏函数时的x和y是什么类型