Fibonacci 數列O(logn)解法


傳統解法

提到斐波那契數列(Fibonacci Sequence),首先想到的是經典的動規(DP)算法。

時間復雜度O(n),這里空間復雜度可以優化到O(1)。代碼如下:

int fib_n(int n)
{
    int dp[3] = {1, 1};
    if (n <= 1) return dp[n];

    for (int i = 2; i <= n; ++i)
        dp[i % 3] = dp[(i - 1) % 3] + dp[(i - 2) % 3];

    return dp[n % 3];
}

 但是初次接觸O(logn)解法有如醍醐灌頂,嘆為觀止……

O(logn)解法

思路來源 1

考慮一個求冪運算。比如求an,一般來說需要n次累乘,時間復雜度顯然是O(n)。實際上可以通過遞歸達到一種更優化的效果:

an = (an/2)2 * an%2

這里的n/2是取整,如5/2=2。這樣就可以實現相當於二分的效果,時間復雜度為O(logn)。

思路來源2

對於Fib數列an(n ≥ 0),可以通過矩陣乘法的方式進行遞推:

進而可以得到:

這樣就(很機智地)把Fib數列問題轉化成了一個求矩陣冪的運算。

解題方法

結合以上思路,首先將其轉化為矩陣求冪問題,然后進行二分,O(logn)解法由此誕生。再次感慨人類清奇的腦洞 _(:з」∠)_

以下是代碼:

int** mult(int** m1, int** m2)
{
    int** res = new int*[2];
    for (int i = 0; i < 2; ++i) res[i] = new int[2];

    res[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];
    res[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];
    res[1][0] = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0];
    res[1][1] = m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1];

    return res;
}

int** recur(int x)
{
    if (x == 0) {
        int** res = new int*[2];
        for (int i = 0; i < 2; ++i) res[i] = new int[2];
        res[0][0] = res[1][1] = 1;
        res[0][1] = res[1][0] = 0;
        return res;
    }
    if (x == 1) {
        int** res = new int*[2];
        for (int i = 0; i < 2; ++i) res[i] = new int[2];
        res[0][1] = res[1][0] = res[1][1] = 1;
        res[0][0] = 0;
        return res;
    }
    int** half = recur(x / 2);
    return mult(mult(half, half), recur(x % 2));
}

// time: O(logn)
int fib_logn(int n)
{
    if (n == 0 || n == 1) return 1;
    int** mat = recur(n - 1);
    return mat[0][1] + mat[1][1];
}
View Code

結果比較

簡單比較一下后者的優化效果,為了是效果更明顯,這里將參數設置成一個較大的數(如109),以下是代碼以及結果:

void test()
{
    const int num = 1e9;
    clock_t t1, t2;

    t1 = clock();
    fib_n(num);
    t2 = clock();
    printf("O(n): %.4f s\n", (double)(t2 - t1) / CLOCKS_PER_SEC);

    t1 = clock();
    fib_logn(num);
    t2 = clock();
    printf("O(logn): %.4f s\n", (double)(t2 - t1) / CLOCKS_PER_SEC);

}

 結果

O(n)算法的速度達到了男子百米的世界頂級水平,而O(logn)只表現出一臉不屑……

好吧,我服……那我把logn的多跑幾次 for (int i = 0; i < 100; ++i) fib_logn(num); 

 

那么結果也很明顯了,O(logn)算法表現驚艷!

THE END 

打賞

免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2022 CODEPRJ.COM