斐波那契数列面试题解法(java)

斐波那契数列经常出现在程序员面试的题目中,本文总结一下斐波那契数列的解法
斐波那契数列(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)的级别。
斐波那契数列的矩阵计算公式如下所示:
斐波那契数列面试题解法(java)_第1张图片
继续进行递推可得下面公式:
斐波那契数列面试题解法(java)_第2张图片
这样就可以写一个矩阵计算的类来求斐波那契数列了

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));
    }
}

你可能感兴趣的:(算法相关)