二叉樹的基礎題目學習(EPI)


1.判斷是個二叉樹是不是平衡二叉樹。

    二叉樹的定義都是利用遞歸的方法,所以二叉樹有着天然的遞歸屬性。所以一般情況下,遞歸解決二叉樹問題中,遞歸解法比較簡潔。平衡二叉樹的定義是左子樹和右子樹均是平衡二叉樹,並且左子樹和右子樹的高度差不超過1,三個條件缺一不可。

    根據遞歸的定義,遞歸實現起來需要返回子樹的高度,又要返回子樹是否平衡的屬性,所以判斷平衡二叉樹的遞歸算法需要傳會兩個參數,所以把遞歸函數原型定義為int balancedTree(TreeNode* root, bool &isBalanced)形式,返回值返回高度,其中引用形參也當做返回值代表子樹是否平衡,這樣實現起來比較容易了。(二叉樹中好多算法遞歸均需要這種返回多個變量的遞歸寫法,可以練習一下。舉印象深刻的兩個類似的:a.二叉樹中最大路徑(路徑定義從任意一節點至任意另一節點),b.最近公共祖先問題,有更多的歡迎補充)。

    一種開闊視野的方法:一種將空間復雜度近一步降低的方法是采用先序遍歷,然后利用一個全局變量實時的記錄stack的最大深度。《計算機編程藝術》第三卷,排序和查找460頁證明了n個節點的平衡樹的高度不會超過hn=1.4405lg(n/2+3)-0.3277。如果遍歷的時候棧的高度超過了這個高度,則這顆樹為不平衡的樹。 

2.首先,設定一個概念,如果一棵樹的左子樹的節點數量和右子樹的節點數量的差的絕對值不大於k,則該節點不是k平衡的。

   題目:給定一個二叉樹,找出二叉樹中一個不是k平衡的節點,且此節點的后繼節點均是k平衡的節點。

    遞歸的查找即可,設定一個全局節點變量,初始化為NULL,遞歸中尋找到節點之后賦值給全局節點變量,遞歸函數中如果全局變量不為NULL,直接返回。遞歸函數返回值為當前樹的節點數量即可。當前樹的節點的數量=左子樹節點數量+右子樹節點數量+1,遞歸結束條件為節點未NULL,返回節點數量0。唯一賦值全局節點變量的地方就是第一次出現左子樹與右子樹差值絕對值超過k的時候,這時候沿着遞歸路徑一路返回,全局變量及為所求。

3.判斷二叉樹是不是鏡像的。

    leetcode中存在原題,比較容易,同樣利用遞歸判斷,不過起始條件為左子樹節點和右子樹節點。遞歸函數isSymmetric(TreeNode *left, TreeNode *right),首先根節點必須相等,其次必須滿足繼續遞歸isSymmetric(left->right,right->let)&&isSymmetric(left->left,right->right),三個條件同時滿足,則才能夠判斷是鏡像樹。遞歸代碼很簡潔。

4.實現一個二叉樹加鎖的機制,限制條件是如果一個節點的后繼節點或者祖先節點已經被鎖定,則該節點不能夠被鎖定。實現isLock(),lock(),和unlock()操作,時間復雜度分別限制為O(1),O(h),O(h),其中h為樹的高度。假設此二叉樹存在指向父節點的指針。

   每個節點添加一個標志位,簡單實現O(1)的isLock()操作。

   另外就是維護lock(),unlock()的時候維護節點的標志位操作了。加鎖和解鎖的時候需要考慮祖先,也需要考慮孩子,祖先可以在O(h)的時間復雜度內遍歷獲取狀態。孩子節點如果逐個考慮可能需要O(n)了。所以不能逐個遍歷,

   這里可以在節點中繼續保存一個變量,其為孩子節點是否被鎖。該變量在lock的時候向上回溯祖先的時候逐個設置標志位即可。

   所以解鎖的時候進行相應的操作,解鎖的時候需要釋放其祖先節點中孩子加鎖的標志,並且也需要更改是否鎖定的標志。

5.給定一個存在父節點指針的二叉樹表示,使用O(1)的額外空間遍歷二叉樹。(先序,中序,后序),不能修改樹的結構。

    Morris方法能夠O(1)的空間復雜度實現二叉樹的遍歷,但是遍歷過程中需要臨時改變樹形結構,所以該遍歷算法不是線程安全的。

    題目存在父節點指針,所以不需要利用棧來進行回溯,可以利用父節點指針回溯,所以可以達到O(1)的時間復雜度。

    算法模板參考利用棧實現的后續遍歷,設置當前節點指針cur,上一個節點指針pre。然后分三種情況討論,

    pre == NULL || pre->left == cur || pre->right == cur 這種屬於traversal down的情況,繼續向下遍歷即可,臨時利用next指針指向下一個節點

    cur->left == pre 這種屬於剛遍歷完左子樹,traversal up的情況

    cur->right == pre 這種屬於剛遍歷完右子樹,traversal up的情況

6.獲得二叉樹中序遍歷的第k個節點的朴素方法遍歷,統計到第k個節點輸出即可,時間復雜度O(n)。假如給定的二叉樹中每個節點包含其所有子樹節點數量和,如何優化尋找第k個節點的算法。時間復雜度可以優化至O(h),h為樹的高度。

    其實題目思路比較容易獲得,快速的縮減問題規模就可以了。查看當前節點的左子樹的數量,

    如果當前節點左子樹的數量等於k,則可知當前節點為查找所需的節點。如果左子樹的節點數量大於或等於k,則可以將問題縮小到尋找左子樹的第k個節點。

    如果當前節點左子樹的數量小於k-1 ,則可知第k個節點在右子樹中,尋找右子樹中第k-(左子樹節點數量+1)即可。

7.根據中序遍歷和后序或者先序遍歷還原二叉樹。

    leetcode中的題目,思路比較簡單,后序/先序可以先確定根節點,根據根節點去分割中序遍歷,遞歸調用左子樹和右子樹即可。

    另外先序和后序無法唯一還原出二叉樹,所以這種題目一般就是中序匹配另外一個遍歷序。

擴展問題:根據給定的一個數組A,構造max樹:max樹定義是根節點為A數組中最大值,假設最大值的坐標為m,則左子樹右A[0:m-1]構成,右子樹由A[m+1,n]構成,遞歸的利用最大值構造左子樹,右子樹。設計高效的構造max樹的方法。

    比較直觀的方法每次尋找最大值即可,這樣每尋找最大值的時間復雜度為O(n),所以總的時間復雜度為O(n^2)。如果想要優化必須考慮從最大值獲取的角度優化。可以分別從左至右,從右至左保存最大值。或者利用單調隊列借助棧的功能完成預處理。

8.如果給定一個包含NULL節點的先序遍歷結果,能夠O(n)還原出二叉樹嗎?后序和中序呢?

    這里借助棧即可,先序和后序,不斷的從棧中彈出兩個節點與當前節點構成小樹壓入棧。最后為一個完整的二叉樹。

    中序的遍歷無法唯一還原出二叉樹。因為所有樹形的遍歷結果序列均相同。

    可以思考下包含NULL的層序遍歷二叉樹(也很簡單)。

9.將二叉樹的所有子節點連接成鏈表。

    遞歸,然后判斷是否子節點,如果子節點連接一下。保存一個全局的頭結點變量即可。

10.設計一個輸出二叉樹外圍節點的方法。外圍節點的解釋:下圖需要輸出的序列A,B,D,G,J,K,L,M,N,I,F,C

    

    前一個題目已經完成了葉子節點的鏈表化了,輸出也不是困難的事情,所以這個題目的難點在於輸出左邊外圍和右邊外圍。

    剛開始思考能否利用Morris遍歷輸出外圍節點,后來畫一個非完全二叉樹之后發現挺復雜的。

    答案的思路非常的簡潔,左邊外圍節點的特點是如果當前節點外圍節點,則該節點的左節點為外圍節點,若左節點為空,則該節點的右子節點為外圍節點。

                                     右邊外圍節點的特點是如果當前節點外圍節點,則該節點的右節點為外圍節點,若右節點為空,則該節點的左子節點為外圍節點。

    注意,左邊外圍和右邊外圍節點的輸出順序,不同。右邊可參考逆序輸出鏈表,遞歸實現相當簡潔。

11.求二叉樹中兩個節點最近公共組先的方法。

     思路比較類似第一個題目的思路,遞歸,但是需要傳遞兩個參數(或者最近公共祖先利用全局變量)

    a.如果兩個節點均在左子樹,則遞歸左子樹尋找最近公共祖先。

    b.如果兩個節點分別在左右子樹,則當前節點返回。

    c.如果當前節點為其中一個節點,則返回當前節點。

   (這個算法從來沒有coding過,需要練習一下,算是基礎的操作)

12.如果二叉樹中包含parent指針,可以優化二叉樹中求兩個節點最近公共祖先的方法嗎?

     有了parent指針,這樣就可以同時從給定的兩個節點向上找最先交叉的節點的,所以問題轉換為兩個單鏈表找最先出現交叉的節點。

     a.方法是三遍掃描,首先針對每個節點,從當前節點統計至末尾節點,分別統計出兩個鏈表的長度,然后長度較大的先走動長度之差的距離,然后兩個指針同時走動,第一次匯合的地方就是最近的公共祖先。

     b.如果使用上述的方法,時間復雜度為最深節點的深度。因為需要統計長度。然后后來該問題繼續深入問了一下能夠進一步優化時間復雜度。

     當時想想確實沒有辦法進一步優化時間復雜度了,思路被阻塞了。完全沒想到空間換時間的策略,題目沒要求空間復雜度,就考慮一下犧牲空間。

     策略是利用hash表,每次遍歷兩個節點,然后添加至hash表。同時向前推進,如果遍歷的節點已經出現在hash表,表示該節點是第一次匯合的節點。(空間換取時間)

13.給定一個字符串S和一系列字符串集合D,找到S中最短的字符串前綴,滿足該前綴不是D集合中任意一個字符串的前綴。

    求最短公共前綴的方法是用Trie樹,這里也需要使用Trie樹的方法。(針對字符串,trie樹的效率高於hash,fb的面試失敗就是沒有第一時間想到這個概念,用的不熟練)

    Trie樹字母表示在邊的擴展中,節點一般存儲true或者false代表是否到達單詞,或者存儲vector<string>容器代表該路徑可達的所有單詞,依據題目來決定Trie樹的結構。

    (需要練習一下)


免責聲明!

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



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