大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)n<=39
题目来源:剑指offer-斐波那契数列
补充说明: 斐波那契数,亦称之为斐波那契数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……
用文字来说,就是斐波那契数列由 0 和 1 开始,之后的斐波那契数列系数就由之前的两数相加,在数学上,斐波那契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=Fn-1+Fn-2(n>=2,n∈N*).
斐波那契数列具有天然的递归性,根据斐波那契数列数学上的定义,我们可以得出其递推公式为:Fn=Fn-1+Fn-2(n>=2,n∈N*),基础情况为 F0=1,F1=1.
public class Solution {
public int Fibonacci(int n) {
//base case
if (n==0){
return 0;
}
if(n==1){
return 1;
}
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
PS:对于递归问题,可画出递归树,帮助分析算法的复杂度。
对于递归解法,我们可以把问题转化为规模缩小了的同类问题的子问题,找出明确的不需要继续进行递归的条件(即基本情况base case),在本题中的基本情况为F0=0,F1=1, 当递归至基本情况后,无需继续递归,而是返回计算结果,最后把子问题的解汇聚成大问题的解.
复杂度分析:
子问题个数,即递归树中节点的总数。显然二叉树节点总数为指数级别,所以子问题个数为 O(2^n).
解决一个子问题的时间,在本算法中,没有循环,只有 f(n - 1) + f(n - 2)f(n−1)+f(n−2) 一个加法操作,时间为O(1)。
所以,这个算法的时间复杂度为 O(2^n),指数级别. 递归过程中,需要在存储递归过程中的运算结果,最大空间为树的高度h(即n).
时间复杂度:O(2^n)
空间复杂度:O(h) 即O(n)
问题分析:
观察递归树,很明显发现了算法低效的原因:存在大量重复计算,运算的规模与n的大小成指数关系,因此这个暴力递归算法虽然简洁明了,但运行效率低下.
由于暴力递归存在大量的重复运算,降低了算法的性能.我们可以把运算结果存储起来,从第0项推导至第n项,避免重复运算.
public class Solution {
public int Fibonacci(int n) {
//基本情况
if (n==0){
return 0;
}
if (n<=1){
return 1;
}
int dp[]=new int[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2;i
在代码中,我们把 dp[i]=dp[i-1]+dp[i-2] 称作状态转移方程, 原因在于我们把 f(n) 当做一个状态 n,这个状态 n 是由状态n−1 和状态 n−2 相加转移而来. 我们不难看出该状态转移方程正代表着暴力递归的过程,只是我们把运算过程的信息保存起来,根据这些信息指导后续的运算,避免重复运算.
面对动态规划问题,我们可以先思考其暴力递归的解法,把暴力递归的过程抽象成状态转移方程,确定可变参数,从基本情况(base case)开始推理,通过状态转移方程,得出最优解,减少冗余运算.
复杂度分析:
需要开辟长度为n+1的dp数组,同时遍历整个数组.
时间复杂度:O(n)
空间复杂度:O(n)
练习:
细心的你会发现,根据斐波那契数列的状态转移方程,当前状态只和之前的两个状态有关,其实并不需要那么长的一个 DP 数组 来存储所有的状态,只要想办法存储之前的两个状态就行了。所以可以进一步优化,把空间复杂度降为 O(1). 请您写出优化后的代码.
这是一个数学方法,通过斐波那契数列通项公式求解,但仅限于标准的斐波那契数列问题求解,无法应对斐波那契数列的变种问题,在此不做展示. 公式如下:
public class Solution {
public int Fibonacci(int n) {
Long fib = Math.round((Math.pow((1 + Math.sqrt(5)) / 2, n)
- Math.pow((1 - Math.sqrt(5)) / 2, n))
/ Math.sqrt(5));
return fib.intValue();
}
}
复杂度分析:
将n代入公式即可得到答案,其中Math.pow(a,b)为求a的b次方、Math.sqrt(a)为求a的正平方根、Math.round(a)为取a最接近的整数(可简单理解为四舍五入取整).对于pow、sqrt、round方法,我们都可以放心地认为其时间复杂度为O(1),因而总的时间复杂度为O(1).我们使用变量fib存储运算结果,因此空间复杂度为O(1).
时间复杂度:O(1)
空间复杂度:O(1)
在计算机发明以前,人们解决问题的方式就是通过经过严格数学证明的公式,找寻答案(如答案三),这确实是一个正确的方式,但也有着许多局限性,如归纳公式并不简单,实际问题复杂多变,难以与公式对应等.计算机的出现使我们多了一种解决问题的方式,即穷举,凭借计算机强大的算力,穷举所有可能性.
十分感谢你看到这里,如果你原本不太了解动态规划,相信现在已经掌握了这个算法的一些设计技巧.算法设计无非就是先思考 “如何穷举”,然后再追求 “如何聪明地穷举”。 在本题中,我们思考出暴力递归的解法,找出问题的状态转移方程,利用状态转移方程实现"聪明地穷举"!
在以后的学习中,我们还会学习更多的算法思想, 但它们无非就是在实现"聪明的穷举".用变量存储运行时的信息,利用这些信息指导后续的运算,这种用空间换时间的思路,是降低时间复杂度的不二法门.
斐波那契数列来自实现生活,有着诸多的变种问题,相信聪明的你能灵活运用动态规划的思想,顺利解决这些问题.
剑指offer-跳台阶
剑指offer-变态跳台阶