[LintCode]斐波納契數(shù)列實(shí)現(xiàn)及優(yōu)化

由于簡(jiǎn)書不支持 Latex 蝗拿,建議去我的博客看原文:斐波納契數(shù)列實(shí)現(xiàn)及優(yōu)化
求關(guān)注沛膳、求交流、求意見俊鱼、求建議刻像。

前言

LintCode 是專注代碼面試的在線評(píng)測(cè)系統(tǒng),有很多代碼題并闲,可以用 Java细睡、C++Python 在線答題帝火,我覺得還不錯(cuò)溜徙,就決定把做一做這些題,然后把題目的實(shí)現(xiàn)犀填、優(yōu)化思路寫下來蠢壹,一來是為了有更深的理解,二來是討論一下還有沒有更好的方法九巡。

題目

LintCode:斐波納契數(shù)列

描述

查找 斐波納契數(shù)列 中第 N 個(gè)數(shù)图贸。
所謂的 斐波納契數(shù)列 是指:

  • 前兩個(gè)數(shù)是 01
  • i 個(gè)數(shù)是第 i-1 個(gè)數(shù)和第 i-2 個(gè)數(shù)的和冕广。

斐波納契數(shù)列的前10個(gè)數(shù)字是:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ...

樣例

給定 1疏日,返回 0
給定 2,返回 1
給定 10撒汉,返回 34

實(shí)現(xiàn)

遞歸實(shí)現(xiàn)

問題分析

根據(jù) 斐波那契數(shù)列 的定義得:

$$
\begin{aligned}
f(1) & = 0\
f(2) & = 1\
f(n) & = f(n - 1) + f(n - 2)\qquad n\in{3,4,5\ldots}
\end{aligned}
$$

根據(jù)上述表達(dá)式最明顯的實(shí)現(xiàn)方式便是遞歸沟优。

實(shí)現(xiàn) - C++

class Solution{
public:
    int fibonacci(int n) {
        if (n == 1) {
            return 0;
        } else if (n == 2) {
            return 1;
        } else {
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
    }
};

實(shí)現(xiàn) - Java

class Solution {
    public int fibonacci(int n) {
        if (n == 1) {
            return 0;
        } else if (n == 2) {
            return 1;
        } else {
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
    }
}

結(jié)果分析

  • 結(jié)果:結(jié)果不盡人意,速度非常慢睬辐,甚至沒有通過 LintCode 的評(píng)測(cè)挠阁。
  • 分析:這種遞歸不同于一般的遞歸,在 n 較大時(shí)溯饵,兩次遞歸調(diào)用中存在大量的重復(fù)運(yùn)算侵俗,導(dǎo)致速度非常慢。

非遞歸實(shí)現(xiàn)

問題分析

在遞歸實(shí)現(xiàn)中瓣喊,由于大量的重復(fù)運(yùn)算導(dǎo)致速度慢坡慌,所以采用非遞歸形式,思路也非常簡(jiǎn)單:從 f(0) 開始根據(jù)公式疊加至 f(n) 藻三。

實(shí)現(xiàn) - C++

class Solution{
    public int fibonacci(int n) {
        if (n == 1) {
            return 0;
        } else if (n == 2) {
            return 1;
        } else {
            int n1 = 0;
            int n2 = 1;
            int sn = 0;
            while (n > 2) {
                sn = n1 + n2;
                n1 = n2;
                n2 = sn;
                n--;
            }
            return sn;
        }
    }
};

實(shí)現(xiàn) - Java

class Solution{
public:
    int fibonacci(int n) {
        if (n == 1) {
            return 0;
        } else if (n == 2) {
            return 1;
        } else {
            int n1 = 0;
            int n2 = 1;
            int sn = 0;
            while (n > 2) {
                sn = n1 + n2;
                n1 = n2;
                n2 = sn;
                n--;
            }
            return sn;
        }
    }
};

結(jié)果分析

  • 結(jié)果:經(jīng)測(cè)試 C++ 最快可以以 10ms 輕松通過 LintCode 的評(píng)測(cè)洪橘。
  • 分析:時(shí)間復(fù)雜度為 o(n) ,空間復(fù)雜度為 o(1) 棵帽,效果不錯(cuò)熄求。
  • 細(xì)節(jié):使用 while 代替 for 節(jié)省了一個(gè) Int(4Byte) 的空間。

遞歸實(shí)現(xiàn)優(yōu)化

問題分析

類似的遞歸重復(fù)計(jì)算問題很多逗概,但未必都可以簡(jiǎn)單的像 斐波那契數(shù)列 問題這么容易化為非遞歸弟晚,那么有沒有辦法遞歸的前提下保證沒有重復(fù)計(jì)算呢?思路也很簡(jiǎn)單:計(jì)算結(jié)果加入緩存。

實(shí)現(xiàn) - C++

class Solution{
public:
    vector<int> buffer;
    int fibonacci(int n) {
        if(n == 1){
            return 0;
        } else if (n == 2){
            return 1;
        }
        int n1, n2, sn;
        if (buffer.size() == 0) {
            buffer.push_back(0);
            buffer.push_back(1);
        }
        if (buffer.size() > n - 2) {
            n1 = buffer[n - 2];
        } else {
            n1 = fibonacci(n - 1);
        }
        if (buffer.size() > n - 3) {
            n2 = buffer[n - 3];
        } else {
            n2 = fibonacci(n - 2);
        }
        sn = n1 + n2;
        if (buffer.size() < n) {
            buffer.push_back(sn);
        }
        return sn;
    }
};

實(shí)現(xiàn) - Java

class Solution {
    ArrayList<Integer> buffer = new ArrayList<Integer>();
    public int fibonacci(int n) {
        if(n == 1){
            return 0;
        } else if (n == 2){
            return 1;
        }
        int n1, n2, sn;
        if (buffer.size() == 0) {
            buffer.add(0);
            buffer.add(1);
        }
        if (buffer.size() > n - 2) {
            n1 = buffer.get(n - 2);
        } else {
            n1 = fibonacci(n - 1);
        }
        if (buffer.size() > n - 3) {
            n2 = buffer.get(n - 3);
        } else {
            n2 = fibonacci(n - 2);
        }
        sn = n1 + n2;
        if (buffer.size() < n) {
            buffer.add(sn);
        }
        return sn;
    }
}

結(jié)果分析

  • 結(jié)果:經(jīng)測(cè)試 C++ 同樣最快可以以 10ms 輕松通過 LintCode 的評(píng)測(cè)卿城。 Java 也跑出了 1269ms 的成績(jī)枚钓,可喜可賀。
  • 分析:雖然空間復(fù)雜度相對(duì)非遞歸提升到了 o(n) 瑟押,不過在不改動(dòng)遞歸結(jié)構(gòu)的前提下搀捷,也算達(dá)到了不錯(cuò)的效果。
  • 細(xì)節(jié):
  • 在枚舉 f(1) 多望、 f(2) 后再聲明變量嫩舟,以節(jié)約內(nèi)存空間。
  • n 是從 1 開始怀偷,buffer 是從 0 開始家厌。
  • f(1)f(2) 要一開始加進(jìn)來,如果遞歸加入會(huì)順序相反椎工,導(dǎo)致結(jié)果出錯(cuò)饭于。

矩陣快速冪實(shí)現(xiàn)

概述

根據(jù)@iFzzzh的提醒,發(fā)現(xiàn)了大大降低時(shí)間復(fù)雜度的方法维蒙。

原理

先介紹一下什么是快速冪镰绎,如下式:

$$
f(n) = a^n\tag{1}
$$

當(dāng) $n$ 為偶數(shù)時(shí)則有:

$$
f(n) = (a{\frac{n}{2}})2=f(\frac{n}{2})^2\tag{2}
$$

當(dāng) $n$ 為奇數(shù)時(shí)則有:

$$
f(n) = (a{\lfloor\frac{n}{2}\rfloor})2 \times a=f(\lfloor\frac{n}{2}\rfloor)^2\times a\tag{3}
$$

顯然 (1) 式時(shí)間復(fù)雜度為 o(n) ,而 (2) (3) 式復(fù)雜度為 o(log_2 n)木西,這就是快速冪,簡(jiǎn)單的來說就是以二分降冪的方式減少計(jì)算步驟随静。

問題分析

類比上述的快速冪法八千,采用矩陣的方式也可以將 斐波那契數(shù)列 化為 a^n 的格式,達(dá)到降冪的效果:

$$
\begin{aligned}
\begin{bmatrix}
f(n)\
f(n-1)\
\end{bmatrix}&=
\begin{bmatrix}
f(n-1)+f(n-2)\
f(n-1)\
\end{bmatrix}\\
&=\begin{bmatrix}
1 & 1\
1 & 0\
\end{bmatrix}\times
\begin{bmatrix}
f(n-1)\
f(n-2)\
\end{bmatrix}\\
&=\begin{bmatrix}
1 & 1\
1 & 0\
\end{bmatrix}^2\times
\begin{bmatrix}
f(n-2)\
f(n-3)\
\end{bmatrix}\
&\qquad\qquad\quad\vdots\
&=\begin{bmatrix}
1 & 1\
1 & 0\
\end{bmatrix}^{n-2}\times
\begin{bmatrix}
f(2)\
f(1)\
\end{bmatrix}\
\end{aligned}
$$

根據(jù) 斐波那契數(shù)列 的定義燎猛,f(1) f(2) 為常數(shù)恋捆,此時(shí)便可以通過快速冪的方式計(jì)算 f(n) 的值了。

實(shí)現(xiàn) - C++

class Solution{
public:
    int fibonacci(int n) {
        if(n == 1){
            return 0;
        }
        if(n == 2){
            return 1;
        }
        int s[2][2];
        rxn(n - 2, s);
        return s[0][0];
    }
    
    void rxn(int n, int result[2][2]){
        if(n == 0){
            result[0][0] = 1;
            result[0][1] = 0;
            result[1][0] = 0;
            result[1][1] = 1;
            return;
        }
        if(n == 1){
            result[0][0] = 1;
            result[0][1] = 1;
            result[1][0] = 1;
            result[1][1] = 0;
            return;
        }
        if(n > 1){
            int s[2][2] = {1, 1, 1, 0};
            int buffer[2][2];
            rxn(n / 2, buffer);
            int buffer2[2][2];
            mul(buffer, buffer, buffer2);
            if(n % 2 == 0){
                result[0][0] = buffer2[0][0];
                result[0][1] = buffer2[0][1];
                result[1][0] = buffer2[1][0];
                result[1][1] = buffer2[1][1];
            }else{
                mul(buffer2, s, result);
            }
        }
    }
    
    void mul(int m1[2][2], int m2[2][2], int result[2][2]) {
        result[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];
        result[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];
        result[1][0] = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0];
        result[1][1] = m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1];
    }
};

實(shí)現(xiàn) - Java

class Solution {
    public int fibonacci(int n) {
        if(n == 1){
            return 0;
        } else if (n == 2){
            return 1;
        }
        int s[][] = new int[2][2];
        rxn(n - 2, s);
        return s[0][0];
    }
    
    public void rxn(int n, int[][] result){
        if(n == 0){
            result[0][0] = 1;
            result[0][1] = 0;
            result[1][0] = 0;
            result[1][1] = 1;
            return;
        }
        if(n == 1){
            result[0][0] = 1;
            result[0][1] = 1;
            result[1][0] = 1;
            result[1][1] = 0;
            return;
        }
        if(n > 1){
            int s[][] = {{1, 1}, {1, 0}};
            int buffer[][] = new int[2][2];
            rxn(n / 2, buffer);
            int buffer2[][] = new int[2][2];
            mul(buffer, buffer, buffer2);
            if(n % 2 == 0){
                result[0][0] = buffer2[0][0];
                result[0][1] = buffer2[0][1];
                result[1][0] = buffer2[1][0];
                result[1][1] = buffer2[1][1];
            }else{
                mul(buffer2, s, result);
            }
        }
    }
    
    public void mul(int[][] m1, int[][] m2, int[][] result) {
        result[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];
        result[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];
        result[1][0] = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0];
        result[1][1] = m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1];
    }   
}

結(jié)果分析

  • 結(jié)果:C++ 最快可以以 10ms 通過 LintCode 的評(píng)測(cè)重绷。 Java 最快可以以 1200ms 通過 LintCode 的評(píng)測(cè)沸停。
  • 分析:可能是由于 LintCode 測(cè)試數(shù)據(jù)不夠大的原因,矩陣快速冪并沒有體現(xiàn)出時(shí)間復(fù)雜度為 o(log_2 n) 應(yīng)有的優(yōu)勢(shì)昭卓,不過根據(jù)其單步計(jì)算量提升愤钾,時(shí)間卻與 非遞歸 遞歸優(yōu)化 達(dá)到同一水平,可以判斷出其效果還是有的候醒。

總結(jié)

理論上講的通的道理只是理論上能颁,小問題到了手上解決掉才能明白。簡(jiǎn)單的問題弄透也不容易倒淫,我記錄一下這個(gè)思路省得忘了伙菊,能夠有人用得上自然更好。當(dāng)然誰要是能給我個(gè)更好的答案才是極好的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末镜硕,一起剝皮案震驚了整個(gè)濱河市运翼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兴枯,老刑警劉巖血淌,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異念恍,居然都是意外死亡六剥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門峰伙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疗疟,“玉大人,你說我怎么就攤上這事瞳氓〔咄” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵匣摘,是天一觀的道長(zhǎng)店诗。 經(jīng)常有香客問我,道長(zhǎng)音榜,這世上最難降的妖魔是什么庞瘸? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮赠叼,結(jié)果婚禮上擦囊,老公的妹妹穿的比我還像新娘。我一直安慰自己嘴办,他們只是感情好瞬场,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涧郊,像睡著了一般贯被。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妆艘,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天彤灶,我揣著相機(jī)與錄音,去河邊找鬼双仍。 笑死枢希,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的朱沃。 我是一名探鬼主播苞轿,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼茅诱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了搬卒?” 一聲冷哼從身側(cè)響起瑟俭,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎契邀,沒想到半個(gè)月后摆寄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坯门,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年微饥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片古戴。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欠橘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出现恼,到底是詐尸還是另有隱情肃续,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布叉袍,位于F島的核電站始锚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏喳逛。R本人自食惡果不足惜瞧捌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望润文。 院中可真熱鬧察郁,春花似錦、人聲如沸转唉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赠法。三九已至,卻和暖如春乔夯,著一層夾襖步出監(jiān)牢的瞬間砖织,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工末荐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侧纯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓甲脏,卻偏偏與公主長(zhǎng)得像眶熬,于是被迫代替她去往敵國(guó)和親妹笆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容