1. 程式人生 > >斐波那契數列面試題解法(java)

斐波那契數列面試題解法(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年後牛的數量。

計算結果一般很大,有時面試時需要使用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;j<matrix.size();j++)
                for(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));
    }
}