斐波那契問題和擴充套件

Grey Zeng發表於2021-10-09

斐波那契數列介紹

斐波那契數,通常用 F(n) 表示,形成的序列稱為 斐波那契數列 。該數列由 0 和 1 開始,後面的每一項數字都是前面兩項數字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

相關題目參考:LeetCode_0509_FibonacciNumber

思路

暴力解法:遞迴版本

    public static int fib(int N) {
        if (N <= 0) {
            return 0;
        }
        if (N == 1 || N == 2) {
            return 1;
        }
        return fib(N - 1) + fib(N - 2);
    }

暴力解法:迭代版本

    public static int fib2(int N) {
        if (N <= 0) {
            return 0;
        }
        if (N == 1 || N == 2) {
            return 1;
        }
        int first = 1;
        int second = 1;
        int result = 0;
        for (int i = 3; i <= N; i++) {
            result = first + second;
            first = second;
            second = result;
        }
        return result;
    }

最優解

如果某個遞迴,除了初始項之外,具有如下的形式

F(N) = C1 * F(N) + C2 * F(N-1) + ... + Ck * F(N-k) ( C1...Ck 和k都是常數)

並且這個遞迴的表示式是嚴格的、不隨條件轉移的, 那麼都存在類似斐波那契數列的優化,時間複雜度都能優化成O(logN),

斐波那契數列的通項公式

F(N) = F(N - 1) + F(N - 2)

斐波那契數列的任意項(以F2,F3,F4為例),都有如下公式:

|F2,F3| * |a,b| = |F3,F4|
          |c,d|

其中,矩陣中a = 0, b = 1, c = 1, d = 1

所以針對斐波那契第N項,有

|F(N),F(N-1)|  = |F2,F1| * |0,1| ^ (N - 2)
                           |1,1| 

所以優化的關鍵在於,求一個矩陣的(N - 2)次方如何更快,我們可以參考求一個整數的N次方如何最快,可以通過快速冪方式來計算。

比如:

求6的5次方

可以這樣來求,

先把5轉換成二進位制0101, 準備一個變數t,初始等於6, 準備一個變數ans, 初始等於1,

從右到左遍歷5的二進位制位,

如果遇到1則:ans *= tt *= t,

如果遇到0則不需要處理ans,只需要t *= t,

直到遍歷完成5的二進位制位,ans即為答案,整個複雜度為 O(logN),

詳細可以參考: x的n次冪, 程式碼為:

public class LintCode_0428_PowXN {

    // 類fabanacci問題
    // pow X N   ( N 轉成2進位制)
    // 複雜度 log(N)
    public static double myPow(double x, int n) { 
        int pow = Math.abs(n == Integer.MIN_VALUE ? n + 1 : n);
        double ans = 1D;
        double t = x;
        while (pow != 0) {
            if ((pow & 1) != 0) {
                ans *= t;
            }
            pow >>= 1;
            t *= t;
        }
        if (n == Integer.MIN_VALUE) {
            ans *= x;
        }
        if (n < 0) {
            ans = 1D / ans;
        }
        return ans;
    }
}

回到斐波那契數列問題,一個矩陣的N次方,也可以優化成O(logN)的解法, 在斐波那契問題中, ans變數初始為單位矩陣,即:

|1,0| 
|0,1| 

t 在斐波那契問題下初始為

|0,1| 
|1,1| 

邏輯和求N的X次冪一樣,只不過N的X次冪中 tans 變數都是數字相乘,而斐波那契問題是矩陣相乘,矩陣相乘的規則請參考線性代數的知識, 完整程式碼如下

    // 最優解 O(log^N)
    public static int fib3(int N) {
        if (N <= 0) {
            return 0;
        }
        if (N == 1 || N == 2) {
            return 1;
        }
        int[][] matrix = matrixPow(new int[][]{{0, 1}, {1, 1}}, N - 2);
        return matrix[0][1] + matrix[1][1];
    }

    public static int[][] matrixPow(int[][] matrix, int n) {
        int[][] ans = new int[][]{{1, 0}, {0, 1}};
        int[][] t = matrix;
        while (n != 0) {
            if ((n & 1) != 0) {
                ans = matrix(t, ans);
            }
            n >>= 1;
            t = matrix(t, t);
        }
        return ans;
    }

    public static int[][] matrix(int[][] A, int[][] B) {
        int[][] result = new int[2][2];
        result[0][0] = A[0][0] * B[0][0] + A[0][1] * B[1][0];
        result[0][1] = A[0][0] * B[0][1] + A[0][1] * B[1][1];
        result[1][0] = A[1][0] * B[0][0] + A[1][1] * B[1][0];
        result[1][1] = A[1][0] * B[0][1] + A[1][1] * B[1][1];
        return result;
    }

類斐波那契問題都可以用如上的優化方法來計算,

例如,某個問題的第N項的通項公式是:

F(N) = 6 * F(N-1) + 3 * F(N-5)

那麼,要求其第N項的值,可以轉換成如下矩陣公式,

|Fn,Fn-1,Fn-2,Fn-3,Fn-4| = |F5,F4,F3,F2,F1|x|5x5|^(N-5)

列出其中前幾個項並帶入求出|5x5| 這個5 乘以 5的矩陣中每個位置的數字,然後參考快速冪的演算法,即可解答。

類斐波那契數列問題

母牛問題

爬臺階問題

貼瓷磚問題

01達標字串的數量問題

什麼時候不能用斐波那契問題的相關公式來解

注意:如果存在條件轉移,那就用不了類斐波那契問題的相關公式 例如:Code_0056_ConvertToLetterString.java

更多

演算法和資料結構筆記

參考資料

相關文章