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

2021-02-18 算法愛好者

(給算法愛好者加星標,修煉編程內功)

來源:代碼隨想錄

相信很多同學對遞歸算法的時間複雜度都很模糊,那麼這篇來給大家通透的講一講。

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

這是為什麼呢?

如果對遞歸的時間複雜度理解的不夠深入的話,就會這樣!

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

面試題:求x的n次方

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

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),此時面試官會說,有沒有效率更好的算法呢。

「如果此時沒有思路,不要說:我不會,我不知道了等等」

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

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

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);
}

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

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

如果認真讀完本篇,相信大家對遞歸算法的有一個新的認識的,同一道題目,同樣是遞歸,效率可是不一樣的!

- EOF -

覺得本文有幫助?請分享給更多人

推薦關注「算法愛好者」,修煉編程內功

點讚和在看就是最大的支持❤️

相關焦點

  • 時間複雜度的分析及排序算法總結
    , O(logn) 時間複雜度的算法隨著數據規模的增⼤,它的優勢就越明顯。相信很多同學對遞歸算法的時間複雜度都很模糊。「同一道題目,同樣使用遞歸算法,有的同學會寫出了O(n)的代碼,有的同學就寫出了O(logn)的代碼」。
  • 時間複雜度分析快速入門:題型分類法
    時間複雜度說難也不難,說簡單也不簡單,但它一定是我們學習算法的過程中過不去的一道坎。這篇文章就想給大家介紹一種快速分析時間複雜度的方法 —— 題型分類法。網絡上關於時間複雜度分析的文章,大部分都充斥著教材裡的陳詞濫調:先講一大堆數學概念,什麼增長速度、漸進複雜度,看得讓人摸不著頭腦。
  • 從一道面試題談談一線碼農應該具備的基本素質
    面試本來就是一個雙向選擇的過程, 面試官和候選人的地位本應該是一個平等的位置, 面試官希望通過簡單的交流溝通可以對候選人的技術, 溝通等(可能主要是技術)有一定了解進而確定候選人是否匹配相應的職位.因為面試時間有限, 1個小時(一般情況)的時間很難去全面了解候選人的技術實力. 所以在面試過程中很難做到完全公平.
  • 一道面試題,看一線碼農應該具備的基本素質
    面試本來就是一個雙向選擇的過程, 面試官和候選人的地位本應該是一個平等的位置,面試官希望通過簡單的交流溝通可以對候選人的技術、溝通等(可能主要是技術)有一定了解進而確定候選人是否匹配相應的職位。因為面試時間有限,很難去全面了解候選人的技術實力,所以在面試過程中很難做到完全公平。舉個簡單的例子。
  • 算法基礎知識 目錄
    ,時間 & 空間複雜度介紹算法和數據結構,而時間,空間複雜度,是算法面試中必考的問題,一旦打錯必掛,所以必須要打好基礎。>算法必備基礎講解具體數據結構前的必備基礎知識,再次探討時間/空間複雜度,了解一些通用的基本算法:遞歸算法,分治法。
  • 【超詳細】一文學會遞歸解題
    來源:碼海作者:kunge遞歸是算法中一種非常重要的思想,應用也很廣,小到階乘,再在工作中用到的比如統計文件夾大小,大到 Google 的 PageRank 算法都能看到,也是面試官很喜歡的考點最近看了不少遞歸的文章,收穫不小,不過我發現大部分網上的講遞歸的文章都不太全面,主要的問題在於解題後大部分都沒有給出相應的時間/空間複雜度
  • 使用JavaScript 遞歸算法,匯總累加題目的分數值
    所謂遞歸算法,是將問題轉化為規模縮小的同類問題的子問題,每一個子問題都用一個同樣的算法去解決。一般來說,一個遞歸算法就是函數調用自身去解決它的子問題。使用遞歸算法實現大題的分數值匯總。我們先來看一下定義,所謂遞歸算法,是將問題轉化為規模縮小的同類問題的子問題,每一個子問題都用一個同樣的算法去解決。一般來說,一個遞歸算法就是函數調用自身去解決它的子問題。遞歸算法的特點:在函數過程中調用自身。在遞歸過程中,必須有一個明確的條件判斷遞歸的結束,既遞歸出口。
  • 備戰跳槽季:大廠面試官總結 16 大常考算法知識點
    作為面試官,在面試過程我會用筆試題的形式考察候選人的思維邏輯能力,通常考察的具體知識點包括鍊表、樹、排序、二分查找等,需要候選人能夠分析出不同算法的時間複雜度和空間複雜度。二分查找(以及變形) 排序(快排) 通過算法面試題的考察,我希望候選人不光可以展示編程能力,還可以通過詳細了解題目,展示自己的溝通能力和推演能力(如何構建題目的思路)。
  • 從一道面試題談談一線大廠碼農應該具備的基本能力
    另外面試能否通過最終強調的是職位匹配,一個蘿蔔一個坑,蘿蔔太大或太小都不一定合適。所以有時候面試沒通過並不是候選人不夠優秀,也有可能是候選人過於優秀(例如本來只想招聘 P6,結果來了一個 P8的候選人肯定不合適)。因為面試時間有限,1個小時(一般情況)的時間很難去全面了解候選人的技術實力,因此在面試過程中很難做到絕對的公平。
  • 我經歷的7輪Google面試
    HR 面試對,第一輪就是 HR 面試,上面流程中的 「Recruiter Prescreen」,其實就是一些計算機相關基礎的填空題和選擇題。幾分鐘的時間,十幾道題目。面試過程中,不需要給予明確的解釋,知道就是知道,不知道就不知道。題目可能會涉及到比如: 快排的時間複雜度是多少? 選擇排序是穩定的排序算法嗎? 等等之類的。
  • 騰訊T4竟然把《數據結構與算法》講明白了,附源碼筆記
    前言算法(Algorithm)是指解題方案的準確而完整的描述,是一系列解決問題的清晰指令,算法代表著用系統的方法描述解決問題的策略機制。也就是說,能夠對一定規範的輸入,在有限時間內獲得所要求的輸出。如果一個算法有缺陷,或不適合於某個問題,執行這個算法將不會解決這個問題。不同的算法可能用不同的時間、空間或效率來完成同樣的任務。一個算法的優劣可以用空間複雜度與時間複雜度來衡量。經歷過校招或者面過網際網路大廠的朋友應該知道,算法和數據結構是不可避免的。
  • 字節大佬Leetcode刷題筆記,看完吊打問你算法的面試官
    第四部分是每日一題,每日一題是在交流群(包括微信和 qq)裡進行的一種活動,大家一起 解一道題,這樣討論問題更加集中,會得到更多的反饋。而且 這些題目可以被記錄下來,日後會進行篩選添加到倉庫的題解模塊。
  • 任性的Google面試官, 這道follow up把我問醉了
    像Google原本就是喜歡出新題變形題,好不容易做完了,面試官冷不防一個follow up,直接懵圈,心態頓時崩了。定期整理自己做過的題目,歸類相似問題,問自己三個問題:題目中哪些條件可以看出這是同類題?同類題目在思維方式上有什麼相似之處?同類題目在代碼實現上有什麼相似之處?
  • 【算法總結】五道常見的算法-二叉樹
    前言前段時間,寫了面試必備的一系列文章,反應還不錯。有一些讀者反饋說,能不能整理一些面試常見的算法。前段時間,我恰好總結了 LeetCode 常見的面試算法題目。https://github.com/gdutxiaoxu/Android_interview剛開始準備刷算法題目的時候,感覺真的是好難,十道題目有九道是不會的。
  • 小米、搜狗、TW等機器學習算法工程師面試總結
    以下機器學習算法工程師的面試題目,出自曉文學習筆記,閱讀更多文章,歡迎加入曉文的公眾號:一面:1、問項目
  • 時空複雜度(時間複雜度/空間複雜度)O(1)、O(n)、O(n^2)、O(log n)、O(n log n)是什麼意思?
    它描述了時空複雜度.大O符號是我在大學裡學過的東西之一,我了解過這個算法的概念。我知道的不算多,可以回答一些基本的問題,僅此而已。從大學畢業以後,我對這個算法的了解基本沒有改變,因為自從我開始工作以來,我沒有使用過它,也沒有聽到任何同事提到過它。所,我想我應該花點時間回顧一下它,並在這篇文章中總結大O符號的基礎知識,以及一些代碼示例來幫助解釋它。什麼是大O符號?
  • Fibonacci 斐波那契數列的幾種寫法、時間複雜度對比
    我看在家修煉編程技術是不錯的選擇,「用Python實現斐波那契數列」是我們在知識星球中每周給大家安排的一道題,你也可以先思考一下有哪些實現方法,說不定哪天面試就能派上用場,終有一日當上CTO贏取白富美從此走上人生巔峰。
  • 評估算法及算法的時間複雜度
    除此之外,通常有三個方面的考慮:(1)算法在執行過程中所消耗的時間;(2)算法在執行過程中所佔資源的大小,例如,佔用內存空間的大小;(3)算法的易理解性、易實現性和易驗證性等等。衡量一個算法的好壞,可以通過前面提出的三個方面進行綜合評估。
  • 通過 JavaScript 學習算法複雜度
    // 每日前端夜話 第474篇// 正文共:1700 字// 預計閱讀時間:10 分鐘在本文中,我們將探討 「二次方」 和 「n log(n)」 等術語在算法中的含義。最壞的情況意味著完成任務需要最多的操作次數;如果你在一秒鐘內就能恢復打亂魔方,那麼你只擰了一圈的話,不能說自己是做得最好的。當你進一步了解算法時,就會發現這非常有用,因為在理解這種關係的同時去編寫代碼,就能知道時間都花在了什麼地方。
  • Google 面試七輪遊, 我失敗了
    面試過程中, 不需要給予明確的解釋, 知道就是知道, 不知道就不知道.  題目可能會涉及到比如: 快排的時間複雜度是多少? 選擇排序是穩定的排序算法嗎? 等等之類的.這也是我參與的所有面試當中, HR 直接參與的」技術」面的. 我理解就通過這一輪面試可以用比較少的成本把一些不合適的候選人直接排除了. 當然這種方法可能不一定適用於所有公司.