經過一道面試題目,講一講遞歸算法的時間複雜度!

相信不少同窗對遞歸算法的時間複雜度都很模糊,那麼這篇來給你們通透的講一講。面試

「同一道題目,一樣使用遞歸算法,有的同窗會寫出了O(n)的代碼,有的同窗就寫出了O(logn)的代碼」算法

這是爲何呢?ide

若是對遞歸的時間複雜度理解的不夠深刻的話,就會這樣!url

那麼我經過一道簡單的面試題,模擬面試的場景,來帶你們逐步分析遞歸算法的時間複雜度,最後找出最優解,來看看一樣是遞歸,怎麼就寫成了O(n)的代碼。spa

面試題:求x的n次方code

想一下這麼簡單的一道題目,代碼應該如何寫呢。最直觀的方式應該就是,一個for循環求出結果,代碼以下:blog

int function1(int x, int n) {
    int result = 1;  // 注意 任何數的0次方等於1
    for (int i = 0; i < n; i++) {
        result = result * x;
    }
    return result;
}

時間複雜度爲O(n),此時面試官會說,有沒有效率更好的算法呢。遞歸

「若是此時沒有思路,不要說:我不會,我不知道了等等」圖片

能夠和麪試官探討一下,詢問:「可不能夠給點提示」。面試官提示:「考慮一下遞歸算法」。ci

那麼就能夠寫出了以下這樣的一個遞歸的算法,使用遞歸解決了這個問題。

int function2(int x, int n) {
    if (n == 0) {
        return 1; // return 1 一樣是由於0次方是等於1的
    }
    return function2(x, n - 1) * x;
}

面試官問:「那麼這個代碼的時間複雜度是多少?」。

一些同窗可能一看到遞歸就想到了O(logn),其實並非這樣,遞歸算法的時間複雜度本質上是要看: 「遞歸的次數 * 每次遞歸中的操做次數」

那再來看代碼,這裏遞歸了幾回呢?

每次n-1,遞歸了n次時間複雜度是O(n),每次進行了一個乘法操做,乘法操做的時間複雜度一個常數項O(1),因此這份代碼的時間複雜度是 n * 1 = O(n)。

這個時間複雜度就沒有達到面試官的預期。因而又寫出了以下的遞歸算法的代碼:

int function3(int x, int n) {
    if (n == 0) {
        return 1;
    }
    if (n % 2 == 1) {
        return function3(x, n / 2) * function3(x, n / 2)*x;
    }
    return function3(x, n / 2) * function3(x, n / 2);
}

面試官看到後微微一笑,問:「這份代碼的時間複雜度又是多少呢?」 此刻有些同窗可能要陷入了沉思了。

咱們來分析一下,首先看遞歸了多少次呢,能夠把遞歸抽象出一顆滿二叉樹。剛剛同窗寫的這個算法,能夠用一顆滿二叉樹來表示(爲了方便表示,選擇n爲偶數16),如圖:

圖片 遞歸算法的時間複雜度

當前這顆二叉樹就是求x的n次方,n爲16的狀況,n爲16的時候,進行了多少次乘法運算呢?

這棵樹上每個節點就表明着一次遞歸併進行了一次相乘操做,因此進行了多少次遞歸的話,就是看這棵樹上有多少個節點。

熟悉二叉樹話應該知道如何求滿二叉樹節點數量,這顆滿二叉樹的節點數量就是2^3 + 2^2 + 2^1 + 2^0 = 15,能夠發現:「這實際上是等比數列的求和公式,這個結論在二叉樹相關的面試題裏也常常出現」

這麼若是是求x的n次方,這個遞歸樹有多少個節點呢,以下圖所示:(m爲深度,從0開始)

圖片


「時間複雜度忽略掉常數項-1以後,這個遞歸算法的時間複雜度依然是O(n)」
。對,你沒看錯,依然是O(n)的時間複雜度!

此時面試官就會說:「這個遞歸的算法依然仍是O(n)啊」, 很明顯沒有達到面試官的預期。

那麼O(logn)的遞歸算法應該怎麼寫呢?

想想剛剛給出的那份遞歸算法的代碼,是否是有哪裏比較冗餘呢。

因而又寫出以下遞歸算法的代碼:

int function4(int x, int n) {
    if (n == 0) {
        return 1;
    }
    int t = function4(x, n / 2);// 這裏相對於function3,是把這個遞歸操做抽取出來
    if (n % 2 == 1) {
        return t * t * x;
    }
    return t * t;
}

再來看一下如今這份代碼時間複雜度是多少呢?

依然仍是看他遞歸了多少次,能夠看到這裏僅僅有一個遞歸調用,且每次都是n/2 ,因此這裏咱們一共調用了log以2爲底n的對數次。

「每次遞歸了作都是一次乘法操做,這也是一個常數項的操做,那麼這個遞歸算法的時間複雜度纔是真正的O(logn)」

此時你們最後寫出了這樣的代碼而且將時間複雜度分析的很是清晰,相信面試官是比較滿意的。

總結

對於遞歸的時間複雜度,畢竟初學者有時候會迷糊,刷過不少題的老手依然迷糊。

「本篇我用一道很是簡單的面試題目:求x的n次方,來逐步分析遞歸算法的時間複雜度,注意不要一看到遞歸就想到了O(logn)!」

一樣使用遞歸,有的同窗能夠寫出O(logn)的代碼,有的同窗還能夠寫出O(n)的代碼。

對於function3 這樣的遞歸實現,很容易讓人感受這是O(logn)的時間複雜度,其實這是O(n)的算法!

int function3(int x, int n) {
    if (n == 0) {
        return 1;
    }
    if (n % 2 == 1) {
        return function3(x, n / 2) * function3(x, n / 2)*x;
    }
    return function3(x, n / 2) * function3(x, n / 2);
}

能夠看出這道題目很是簡單,可是又很考究算法的功底,特別是對遞歸的理解,這也是我面試別人的時候用過的一道題,因此整個情景我才寫的如此逼真,哈哈。

大廠面試的時候最喜歡用「簡單題」來考察候選人的算法功底,注意這裏的「簡單題」可並不必定真的簡單哦!

若是認真讀完本篇,相信你們對遞歸算法的有一個新的認識的,同一道題目,一樣是遞歸,效率但是不同的!

就醬,「代碼隨想錄」是技術公衆號裏的一抹清流,值得介紹給身邊的朋友同窗們!

打算從頭開始打卡的錄友,能夠在「算法彙總」這裏找到歷史文章,不少錄友都在從頭打卡,你並不孤單!

圖片

相關文章
相關標籤/搜索