斐波那契數列面試題解法(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));
}
}