Fabonacci 數列問題


今天面試,被問到求fabonacci數列的第n個數這個問題,當時用O(n)複雜度的迭代方法做出來了,然後面試官繼續問了如何實現O(log(n))的時間複雜度的算法,最後還問了n大概爲多大時結果就會溢出(用unsigned int來保存結果)。


Fabonacci數列

Fabonacci數列是指數列中的任一項都等於前兩項之後,通項公式爲:

F(n)=F(n1)+F(n2)

一般令F(0)=1F(1)=1 ,則Fabonacci數列前幾項爲 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …


O(n)時間複雜度的方法

第一種方式就是用迭代的方式,每次只需要保存兩個連續的值,就可以生成數列中的下一個數了。

// O(n) iterative method
int64_t Fabonacci1( int n ) {
  if ( n <= 1 ) return n;
  int64_t prev = 0, cur = 1, next = 0;
  for ( int i = 2; i <= n; ++i ) {
    next = prev + cur;
    prev = cur;
    cur = next;
  }
  return cur;
}

O(logn)時間複雜的方法一

Fabonacci數列有一個變形,可以由此得到通項公式。然後利用通項公式加速計算。

G(n)=[F(n+1)F(n)F(n)F(n1)]=[1110]×[F(n)F(n1)F(n1)F(n2)]=[1110]n1×[F(2)F(1)F(1)F(0)]=[1110]n

有了通項公式,就可以直接求出F(n)了。明顯的對於G(n),可以求G(n/2),再由G(n/2)*G(n/2)來得到G(n),然後每次這樣折半,最後到G(1)或G(0)。不過需要注意的是要區分n爲奇數和偶數的情況,具體來說,當n爲奇數的時候,n/2+n/2 = n - 1,所以當n爲奇數的時候指數需要加1。這樣算法就是O(logn)的時間複雜度啦。

// O(logn) method1
void multiply( int64_t m[2][2], int64_t n[2][2] )
{
  auto x = m[0][0] * n[0][0] + m[0][1] * n[1][0];
  auto y = m[0][0] * n[0][1] + m[0][1] * n[1][1];
  auto z = m[1][0] * n[0][0] + m[1][1] * n[1][0];
  auto w = m[1][0] * n[0][1] + m[1][1] * n[1][1];

  m[0][0] = x; m[0][1] = y;
  m[1][0] = z; m[1][1] = w;
}

void power( int64_t num[2][2], int n ) {
  if ( n <= 1 ) return;
  int64_t m[2][2] = { { 1, 1 }, { 1, 0 } };

  power( num, n / 2 );
  multiply( num, num );

  if ( n & 0x1 )
    multiply( num, m );
}

/*
  | F(n+1) F(n)   |    | 1 1 |n-1    | F(2) F(1) |    | 1 1 |n
  | F(n)   F(n-1) |  = | 1 0 |    *  | F(1) F(0) | =  | 1 0 |
*/
int64_t Fabonacci2( int n ) {
  int64_t m[2][2] = { { 1, 1 }, { 1, 0 } };
  if ( n == 0 ) 
    return 0;
  power( m, n - 1 );
  return m[0][0];
}

O(logn)時間複雜的方法二

上面利用通項公式求Fabonacci數列的方式還是比較麻煩的。下面給出一個更簡單一點的方式。

由上面可知 G(m+n)=G(n)G(m) ,所以得到:

[F(m+n+1)F(m+n)F(m+n)F(m+n1)]=[F(n+1)F(n)F(n)F(n1)]×[F(m+1)F(m)F(m)F(m1)]

進一步可以得到:

F(m+n1)=F(m)F(n)+F(m1)F(n1)

繼續變形,得到:

F(2n)=F(n+1)F(n)+F(n)F(n1)F(2n1)=F(n)2+F(n1)2

所以總結就是:
當n爲偶數時,令k=n/2,

F(n)=(2F(k1)+F(k))F(k)

當n爲奇數時,令k=(n+1)/2,

F(n)=F(k)F(k)+F(k1)F(k1)
// O(logn) method2
int64_t Fabonacci3( int n ) {
  static vector<int64_t> f;
  if ( f.size() <= n ) {
    f.resize( 2 * n, 0);
  }

  if ( n == 0 ) return 0;
  if ( n == 1 || n == 2 ) return ( f[n] = 1 );

  if ( f[n] ) return f[n];

  // if n is odd then n&1 is 1
  if ( n & 1 ) {
    int k = ( n + 1 ) >> 1;
    f[n] = Fabonacci3( k ) * Fabonacci3( k ) 
      + Fabonacci3( k - 1 ) * Fabonacci3(k - 1);
  }
  else {
    int k = n >> 1;
    f[n] = ( 2 * Fabonacci3( k - 1 ) + Fabonacci3( k ) ) * Fabonacci3( k );
  }
  return f[n];
}

Fabonacci通用公式

其實fabonacci數列是有通用的公式的,可以直接求得結果。

F(n)=αnβn5,α=1+52,β=152,

其中αβ 是方程x2=x+1 的兩個根。
下面來證明這個表達式。原方程兩邊同時乘以xn2 ,得到xn=xn1+xn2 ,所以進一步有:

αn=αn1+αn2βn=βn1+βn2

U(n)=aαn+bβn=aαn1+bβn1+aαn2+bβn2=U(n1)+U(n2) ,就構造出了和Fabonacci一樣的遞推公式了,再令U(0)=0,U(1) = 1;那麼U(n) 就是Fabonacci數列了。

U(0)=a+b=0U(1)=aα+bβ=1

求得 a=1αβ=15,b=a
所以F(n)=U(n)=αnβn5


Fabonacci 數列上下界

F(n)=αnβn5,α=1+52,β=152,

得到公式了其實就可以比較容易得到上界限,|β|<0.5n 趨於無窮大時βn 趨於 0 ,所以F(n) 上界就是

αn5=1.62n5

下界其實是

1.5n5(n11)

可以用歸納法證明。

參考

http://www.geeksforgeeks.org/program-for-nth-fibonacci-number/
http://math.stackexchange.com/questions/571433/exponential-lower-bound-for-fibonacci-numbers
http://math.stackexchange.com/questions/674533/prove-upper-bound-big-o-for-fibonaccis-sequence

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章