斐波那契数列经常出现在程序员面试的题目中,本文总结一下斐波那契数列的解法
斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
题目1:给定整数N,代表台阶数,小明一次可以上一个台阶或一次上两个台阶(因为怕扯着蛋,一次最多上两个台阶),问上N节台阶总共有多少种方法?
问题分析:记f(n)表示上n节台阶的所有方法总数。小明初始站在第0级台阶上,f(0)=1;上第一节台阶,只能一次上去,f(1) = 1;小明上楼梯时要么经过第一级台阶,要么越过第一级台阶(越过第一级台阶只能站第二级台阶上),所以经过一步之后问题变成f(n) = f(n-1) + f(n-2)。f(n-1)表示站在第一级台阶上还剩多少种走法,f(n-2)表示站在第二级台阶上还是多少种走法。
题目2:假设农场中成熟的母牛每年只会生1头小母牛,并且永远不会死。第一年农场有1只成熟的母牛,从第二年开始,母牛开始生小母牛。给定整数N,求出N年后牛的数量。
题目3:我们有一个2xN的长条形棋盘,然后用1x2的骨牌去覆盖整个棋盘。对于这个棋盘,一共有多少种不同的覆盖方法呢?
详见http://hihocoder.com/problemset/problem/1143
计算结果一般很大,有时面试时需要使用BigInteger来表示。有时也会让结果mod一个数输出,这里假设mod19999997作为结果输出。
首先根据定义轻松写出暴力递归的代码
import java.util.Scanner;
class Fibonacci{
//int能表示的最大值的一半,两个小于halfMax的值相加结果不会越界
private static int halfMax = Integer.MAX_VALUE>>1;
//求斐波那契数列第n项,结果对m求余
public static int fibonacci(int n,int m)
{
if(n<1)
return 0;
if(n==1 || n==2)
return 1;
if(m<=halfMax){
int result = fibonacci(n-1,m) + fibonacci(n-2,m);
//return result%m;
//本来应该进行求余操作,由于result不会大于2m,这里用减法速度回更快
return result>=m?result-m:result;
}
else{
long result = (long)fibonacci(n-1,m) + fibonacci(n-2,m);
return (int) (result>=m?result-m:result);
}
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
System.out.println(Fibonacci.fibonacci(n, 19999997));
}
}
面试的时候写成这样肯定是不合格的,这样写的时间复杂度为2的n次方,计算30以内的还可以,更大的数就需要等待了。通过分析可以发现计算中有大量重复的计算,比如f(n-2),计算f(n)的时候计算一次,计算f(n-1)的时候又计算一次。可以通过建立缓存来解决重复计算问题。代码如下
import java.util.Scanner;
class Fibonacci{
//int能表示的最大值的一半,两个小于halfMax的值相加结果不会越界
private static int halfMax = Integer.MAX_VALUE>>1;
//保存求解中的值
private static int value[];
//value中的值是否被计算
private static boolean used[];
//求斐波那契数列第n项,结果对m求余
private static int getFibonacci(int n,int m)
{
if(used[n])
return value[n];
if(m<=halfMax){
int result = getFibonacci(n-1,m) + getFibonacci(n-2,m);
//return result%m;
//本来应该进行求余操作,由于result不会大于2m,这里用减法速度回更快
result = result>=m?result-m:result;
value[n] = result;
used[n] = true;
return result;
}
else{
long result = (long)getFibonacci(n-1,m) + getFibonacci(n-2,m);
result = result>=m?result-m:result;
value[n] = (int)result;
used[n] = true;
return value[n];
}
}
public static int fibonacci(int n,int m)
{
if(n<1)
return 0;
if(n==1 || n==2)
return 1;
value = new int[n+1];
value[0] = 0;
value[1] = 1;
value[2] = 1;
used = new boolean[n+1];
used[0] = true;
used[1] = true;
used[2] = true;
return getFibonacci(n,m);
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
System.out.println(Fibonacci.fibonacci(n, 19999997));
}
}
斐波那契数列每一项等于前两项的和,我们可以从前往后来计算,使用循环就可以解决了。
import java.util.Scanner;
class Fibonacci{
//求斐波那契数列第n项,结果对m求余
public static int fibonacci(int n,int m)
{
if(n<1)
return 0;
if(n==1 || n==2)
return 1;
int value[] = new int[n+1];
value[1] = 1;
value[2] = 1;
//后一项等于前两项的和
for(int i=3;i<=n;i++){
long result = (long)value[i-1]+value[i-2];
value[i] = (int) (result>m?result-m:result);
}
return value[n];
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
System.out.println(Fibonacci.fibonacci(n, 19999997));
}
}
上面的方法计算的时候开辟的大块空间存储中间计算结果。如果需要经常求不大的数的斐波那契数列,我们可以把一定范围内的结果保存起来,每次需要的时候拿出来就可以了。如果只让我们求一次斐波那契数列的第n项,或者n比较大,上面的方法就不好使了。观察发现我们只需要3个数的空间就够了,每次计算f(n)时f(n-3)就没用了,可以把存放f(n-3)的空间用来存放f(n)。使用循环数组可以实现,这里为了不计算mod3,把数组长度设置为了4。
import java.util.Scanner;
class Fibonacci{
//求斐波那契数列第n项,结果对m求余
public static int fibonacci(int n,int m)
{
if(n<1)
return 0;
if(n==1 || n==2)
return 1;
//循环队列用来存放计算结果,这里用4不用3为了计算方便
int value[] = new int[4];
value[1] = 1;
value[2] = 1;
//后一项等于前两项的和
for(int i=3;i<=n;i++){
long result = (long)value[(i-1)&3]+value[(i-2)&3];
value[i&3] = (int) (result>m?result-m:result);
}
return value[n&3];
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
System.out.println(Fibonacci.fibonacci(n, 19999997));
}
}
如果一般小公司面试写成这样就可以了,如果面试大公司需要写出log(n)时间复杂度的算法才行。对于像类似
f(n) = a*f(n-1)+b*f(n-2)+c*f(n-3)…这样的公式的求值可以用矩阵乘法来完成。由于矩阵乘法有结合律,A的n次方可以用A的n/2次方来求,时间复杂度可以降到log(n)的级别。
斐波那契数列的矩阵计算公式如下所示:
继续进行递推可得下面公式:
这样就可以写一个矩阵计算的类来求斐波那契数列了
import java.util.Scanner;
//方形矩阵
class Matrix{
private long arr[][];
Matrix(int size){
arr = new long[size][size];
}
Matrix(){
arr = new long[2][2];
}
//往矩阵的n行m列赋值
public void setValue(int n,int m,int value){
arr[n][m] = value;
}
public long getValue(int n,int m){
return arr[n][m];
}
public int size(){return arr.length;}
//该矩阵乘matrix
public Matrix muliMatrix(Matrix matrix,int m){
if(this.size() != matrix.size())
return null;
Matrix result = new Matrix(this.size());
for(int i=0;i<this.size();i++)
for(int j=0;jfor(int k=0;k<this.size();k++){
result.arr[i][j] += this.arr[i][k]*matrix.arr[k][j];
//中间结果对m求余
result.arr[i][j] %= m;
}
return result;
}
//矩阵幂运算,n表示幂,m用来求余
public Matrix matirxPower(int n , int m){
if(n==1)
return this;
Matrix result = new Matrix(this.size());
result = matirxPower(n>>1 , m);
result = result.muliMatrix(result , m);
if((n&1) == 1){
result = result.muliMatrix(this, m);
}
return result;
}
}
class Fibonacci{
public static int fibonacci(int n,int m){
if(n<1)
return 0;
if(n==1 || n==2)
return 1;
Matrix factor = new Matrix(2);
factor.setValue(0, 0, 1);
factor.setValue(0, 1, 1);
factor.setValue(1, 0, 1);
factor = factor.matirxPower(n-2, m);
Matrix init = new Matrix(2);
init.setValue(0, 0, 1);
init.setValue(1, 0, 1);
Matrix result = factor.muliMatrix(init, m);
return (int) result.getValue(0, 0);
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
System.out.println(Fibonacci.fibonacci(n+1, 19999997));
}
}