斐波那契数列
基础
如果我们需要重复地多次计算相同的问题,则通常可以选择用递归或者循环两种不同的方法。递归是在一个函数的内部调用这个函数自身。而循环则是通过设置计算的初始值及终止条件,在一个范围内重复运算。比如求1+2+-+ « ,我们可以用递归或者循环两种方式求出结果。
通常递归的代码会比较简洁。在上面的例子中,递归的代码只有一条语句,而循环则需要4 条语句。在树的前序、中序、后序遍历算法的代码中,递归的实现明显要比循环简单得多。在面试的时候,如果面试官没有特别的要求,则应聘者可以尽量多采用递归的方法编程。
虽然有简洁的优点,但它同时也有显著的缺点。递归由于是函数调用自身,Ifl]'函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而且往栈里压入数据和弹出数据都需要时间。这就不难理解h述的例子中递归实现的效率不如循环。
另外,递归中冇可能很多计算都是重复的,从而对性能带来很大的负面影响。递归的本质是把一个问题分解成两个或者多个小问题。如果多个小问题存在相互重叠的部分,就存在重复的计算。在面试题10 “斐波那契数列”及面试题 60 个骰子的点数”中,我们将详细地分析递归和循环的性能R 别。
通常应用动态规划解决问题时我们都是用递归的思路分析问题,但出于递归分解的子问题中存在大量的重复,因此我们总是用自下而上的循环来实现代码。我们将在面试题14 “剪绳子”、面试题47 “礼物的最大价值”及面试题48 “最长不含重复字符的子字符串”中详细讨论如何用递归分析问题并基于循环写代码。
除效率之外,递归还有可能引起更严重的问题:调用栈溢出。在前面的分析中提到需要为每一次函数调用在内存栈中分配空间,而每个进程的栈的容量是有限的。当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。在上述例子中,如果输入的参数比较小,如 10,则它们都能返回结果55。但如果输入的参数很大,如 5000,那么递归代码在运行
的时候就会出错,但运行循环的代码能得到正确的结果丨2502500。
题目
思路
代码
思路1
public static void main(String[] args) {
System.out.println("请输入n");
Scanner sc = new Scanner(System.in);
System.out.println(fib(sc.nextInt()));
}
public static int fib(int N) {
if(N == 0 || N ==1){
return N;
}
return fib(N-1)+fib(N-2);
}
思路2
public static int fib(int N) {
if(N == 0 || N ==1){
return N;
}
int fibOne = 0;
int fibTwo = 1;
int fibN =0;
for(int i =2;i<= N;i++){
fibN = fibOne + fibTwo;
fibOne = fibTwo;
fibTwo = fibN;
}
return fibTwo;
}
思路3
动态规划
class Solution {
public int fib(int N) {
if (N <= 1) {
return N;
}
int[] dp = new int[N + 1];
dp[1] = 1;
for (int i = 2; i <= N; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[N];
}
}
LeetCode
示例 1:
输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1.
示例 2
输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2.
示例 3:
输入:4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3.
提示:
0 ≤ N ≤ 30