题目1:求斐波那契数列的第n项。
写一个函数,输入n,求斐波那契数列(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
-
方法1:效率很低效的解法,挑剔的面试官不会喜欢
很多C语言的教科书在讲述递归函数的时候,都会拿 Fibonacci 作为例子,因此很多的应聘者对这道题的递归解法都很熟悉。
下面是实现代码:
我们教科书上反复用这个问题来讲解递归的函数,并不能说明递归的解法最适合这道题目。面试官会提示我们上述递归的解法有很严重的效率问题要求我们分析原因。
我们以求解 f(10) 为例来分析递归的求解过程。想求得f(10),需要先求出f(9)和f(8).同样求f(9),需要先求得f(8)和f(7)。我们用树来构造这种依赖关系。如图所示:
我们不难发现在这颗树中有很多的节点是重复的,而且重复的节点数会随着 n 的增大而急剧增加,这意味着计算量会随着 n 的增大而急剧增大。事实上,用递归的方法计算的时间复杂度是以n的指数的方式递增的。读者不妨求Fibonacci的第100项试试,感受一下这样的递归会慢到什么程度。
-
方法2:面试官期待的实用解法 (重要)
其实改进的方法比并不复杂。上述的递归代码之所以慢是因为重复的计算太多,我们只要想避免重复计算就型了。比如我们可以把已经得到的数列中间项保存起来,如果下次需要计算的时候我们先查找一下,如果前面已经计算过就不用重复计算了。
更简单的方法是从下往上计算,首先计算f(0)和f(1)算出f(2),再根据f(1)和f(2)算出f(3)……依次类推就可以算出第n项了。很容易理解,这种思路的时间复杂度为O(n)。实现代码如下:
public class FibonacciDemo1 {
// 写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项
public long fibonacci(int n){
long result = 0;
long preOne = 1;
long preTwo = 0;
if(n == 0){
// 当n=0时
return result;
}else if(n == 1){
// 当n=1时
return preOne;
}else{
// 当n>1时
for (int i = 2; i <= n; i++) {
result = preOne + preTwo;
preTwo = preOne;
preOne = result;
}
}
return result;
}
public static void main(String[] args) {
FibonacciDemo1 fib = new FibonacciDemo1();
System.out.println(fib.fibonacci(10)); // 55
}
}
-
方法3:时间复杂度O(logn)但不够实用的解法
通常面试到这里就差不多了,尽管我们还有比这更快的O(logn)解法,由于这种算法需要用到一个很生僻的数学公式,因此很少有面试官会要求我们掌握。不过以防不时之需,我们还是介绍一下这种算法。
我们先来介绍一个数学公示:
- 三种解法比较:
题目2:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级台阶总共有多少种跳法。