文章目录
- 一、斐波那契数列问题
- 1.1 题目
- 1.2 什么是斐波那契数列
- 1.3 效率很低的解法:递归
- 1.4 递归缺点分析
- 二、比较好的解决办法
- 2.1 保存数列中间项
- 2.2 从下往上计算
- 三、公式法
- 四、青蛙跳台阶问题
- 4.1 题目及分析
- 4.2 代码实现
一、斐波那契数列问题
1.1 题目
写一个函数,输入n,求斐波那契数列的第n项。
斐波那契数列定义如下:
1.2 什么是斐波那契数列
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称“兔子数列”,其数值为:1、1、2、3、5、8、13、21、34……
在数学上,这一数列以如下递推的方法定义:F(0)=1,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)。
1.3 效率很低的解法:递归
public int Fibonacci(int n){if(n <= 0){return 0;}if(n <= 1){return 1;}return Fibonacci(n-1) + Fibonacci(n-2);
}
1.4 递归缺点分析
递归的代码虽然简洁,但是这并不是一个很好的解法,因为存在很严重的效率问题。
从图中可以看出,想要求f(n),就要先求出f(n-1)和f(n-2),同样想要求f(n-1),就要先求出f(n-2)和f(n-3)。我们不难发现这棵树中有很多重复的节点,并且重复节点随着n增大急剧增加,这个时间复杂度以n的指数形式递增。
二、比较好的解决办法
2.1 保存数列中间项
递归之所以慢是因为重复计算太多,我们可以把已经得到的中间项保存起来,下次再使用的时候先查找,如果前面已经计算过的就不用再重复计算了。
public int Fibonacci(int n){int[] result = {0,1};if(n < 2){return result[n];}int[] dp = new int[n+1];dp[0] = 0;dp[1] = 1;for(int i = 2; i <= n; i++){dp[i] = dp[i-1]+dp[i-2];}return dp[n];
}
2.2 从下往上计算
更为简单的方法是从下往上计算,首先根据f(0)和f(1)算出f(2),然后再根据f(1)和f(2)算出f(3)……
时间复杂度为f(n)。
public int Fibonacci(int n){int[] result = {0,1};if(n < 2){return result[n];}int p = 0;int q = 1;int res = 0;for(int i = 2; i <= n; i++){res = p + q;p = q;q = res;}return res;
}
三、公式法
public int fib(int n) {double temp=Math.sqrt(5);double ans=(1/temp)*(Math.pow((1+temp)/2,n)-Math.pow((1-temp)/2,n));return (int)Math.round(ans);//round()函数参数为double型时,返回一个最接近该参数的long型数,参数为float型时,返回一个最接近的int值
}
这种方法的时间复杂度为O(log n),不过公式比较生僻。
四、青蛙跳台阶问题
4.1 题目及分析
题目:
一只青蛙一次可以跳上1级台阶,也可以跳上两级台阶。求该青蛙跳上一个n级台阶总共有多少种跳法。
分析:
简单的情况:
只有一级台阶,只有一种跳法。
有两级台阶,有两种跳法:一种是跳两个一级,一种是跳一个两级。
一般情况:
跳n级台阶看成n的函数f(n)。当n>2时,第一次跳的时候有两种不同的选择,一是第一次只跳一级,跳法数目等于后面n-1级台阶的跳法数目,即f(n-1);二是第一次跳两级,此时跳法等于后面n-2级台阶的跳法数目,即f(n-2)。因此n级台阶的跳法总数f(n) = f(n-1) + f(n-2)。可以看出这就是一个斐波那契数列。
4.2 代码实现
public int frogJump(int n){if(n == 1 || n == 2){return n;}int p = 1;int q = 2;int res = 0;for(int i = 3; i <= n; i++){res = p + q;p = q;q = res;}return res;
}