之前有同学去面试,被问到斐波那契数列的递归求法。其实大多数人会认为这个非常简单,很快给出答案。如下:
根据定义很容易写出的斐波那契数列:
int Fibonacci(int n) {
if (n<=2) {
return 1;
}
else {
return Fibonacci(n-1) + Fibonacci(n-2);
}
}
但是面试官接着问这样不断的压栈很浪费内存空间,问是否可以优化?可能很多人给不出其他解法了,或者说感觉其他解法如果也是递归的话,也需要压栈,一样会占用内存空间。其实,面试官想考察的是尾递归
。
什么是尾递归 ?
在计算机科学里,尾调用是指一个函数里的最后一个动作是一个函数调用的情形即这个调用的返回值直接被当前函数返回的情形。这种情形下称该调用位置为尾位置。若这个函数在尾位置调用本身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归,是递归的一种特殊情形。尾调用不一定是递归调用,但是尾递归特别有用,也比较容易实现。
尾调用的重要性在于它可以不在调用栈上面添加一个新的堆栈帧——而是更新它,如同迭代一般。尾递归因而具有两个特征: 调用自身函数(Self-called); 计算仅占用常量栈空间(Stack Space)。 而形式上只要是最后一个return语句返回的是一个完整函数,它就是尾递归。
简单理解,就是处于函数尾部的递归调用本身的情形下,前面的变量状态都不需要再保存了,可以释放,从而节省很大的内存空间。在前面的代码中,明显在调用递归调用Fibonacci(n-1)
的时候,还有Fibonacci(n-2)
没有执行,需要保存前面的状态,因此开销较大的。
于是,我们可以改写这个斐波那契数列:
int Fibonacci(int n, int a, int b) {
if (n<=2) {
return b;
}
else {
return Fibonacci(n-1, b, a+b);
}
}
详细的可以参考上面给出的尾递归链接。
关于斐波那契数列的变形
青蛙跳台阶的问题
这个问题很常见,就是找到 F[n]=F[n−1]+F[n−2] F[n]=F[n−1]+F[n−2]递推关系,转化为斐波那契数列问题方法求解。
青蛙跳台阶问题的变形
青蛙可以从 1,2,3...n−1 1,2,3...n−1跳到n层,数学归纳法。
再变形,一个2*N的矩形,用1*2的小矩形覆盖,有多少种方法。
仍然归纳:
如果在最左边竖着放小矩形,则转化为求解F[N-1]。
如果在最左边横着放小矩形,则一定要有个同样的小矩形也横着与其并排放,这转化为求解F[N-2]
因此,转化为了斐波那契数列问题, F[N]=F[N−1]+F[N−2]