開心一刻
一天,有個男粉絲跟我述苦
粉絲:我喜歡一個女人,那個女人也喜歡我
我:你們都是多大
粉絲:我今年23,她今年26
我:女大三,抱金磚,我覺得可以呀,年齡不是問題
粉絲:她家裡不同意
我:她家裡為什麼不同意,嫌你條件不好,還是嫌你年齡太小
粉絲:她老公不同意
我:她老公... 你給我滾
前情回顧
對二叉樹的遍歷還不瞭解的,先去看看:二叉樹的遍歷 → 不用遞迴,還能遍歷嗎
簡單來說,深度遍歷用 棧 輔助實現,廣度遍歷用 佇列 輔助實現
不管是遞迴(系統棧)實現,還是 棧 + 迭代 實現,深度遍歷的額外空間複雜度都是:O(n)
那有沒有額外空間複雜度 O(1) 的方法來實現二叉樹的深度遍歷呢?(O(1) 是指常數級別,而非字面 1 的意思)
還真有:morris traversal,只是遍歷過程會破壞二叉樹的結構,所以存在恢復二叉樹結構的過程,具體實現可檢視:Morris Traversal方法遍歷二叉樹(非遞迴,不用棧,O(1)空間)
不是很好理解,大家結合二叉樹樣本結構,去逐行 debug 程式碼,看看二叉樹的遍歷、結構變化,慢慢的就有感覺了
實戰案例
當我們對二叉樹的遍歷有了一定的瞭解之後,我們就可以嘗試解決一些二叉樹的問題了
最大寬度
從根節點開始,一層一層往下統計,最大寬度即是節點數最多的那一(些)層的節點數量,例如
最大寬度就是 3
很明顯,這是一個寬度(廣度)遍歷,那就需要用到 佇列 來輔助實現
光實現寬度遍歷還不夠,還需要統計每一層的節點數,然後找出最大寬度;那如何統計每一層的節點數?
我們可以先用雜湊表記錄每個節點所處的層次,實現如下
相信大家都能看懂這個程式碼,就是在寬度遍歷的基礎上,對每個節點進行層次標記
標記完之後,再遍歷 levelMap ,完成層次的個數統計?
我們知道雜湊表一般是無序的,再遍歷 levelMap 進行層次的個數統計,並沒那麼簡單;非要較勁,也是可以實現的,但沒比較
寬度遍歷本身就是逐層進行的,當進行到下一層時,上一層肯定全部遍歷完了,所以當遍歷下一層的時候,上一層的節點數就應該統計出來
我們來看完整程式碼
簡單點來說,就是在寬度遍歷的基礎上加入了統計的邏輯,所以重點是寬度遍歷
只要能夠理解寬度遍歷,上述程式碼就很容易理解
我們再延伸下,如果不用雜湊表,還能實現嗎?
雜湊表的作用看似是記錄每個節點所在的層次,實際就是用來判斷當前層次是否處理完,基於此我們可以改造下
用兩個節點變數( curEnd 、 nextEnd )分別記錄當前層的最後一個節點和下一層的最後一個節點;遍歷當前層的時候,移動 nextEnd
當遍歷完當前層節點( cur == curEnd ), nextEnd 剛好來到來到下一層的最後一個節點,將 nextEnd 賦值給 curEnd ( curEnd = nextEnd; ),開始下一層的遍歷與統計
如此反覆,找到最大寬度;程式碼如下
是不是很有意思?
我們再來延伸下,找出最大寬度的同時,找出最大寬度所在的層(可能多層),該如何實現?
這個就交給你們自己去實現了
路徑總和
給定二叉樹的根節點 root 和一個整數目標和 targetSum ,找出所有 從根節點到葉子節點 路徑總和等於給定目標和的路徑(葉子節點 是指沒有子節點的節點)
示例:
先序遍歷,找出所有路勁,過濾出路徑上節點之和等於 targetSum 的路徑
比較簡單,直接看程式碼
有兩個注意點:1、為什麼不直接將 curPath 新增到 allPath,而是 copy 一份之後將新的新增到 allPath;2、為什麼要回溯
第 1 點,正是由於回溯,導致 curPath 中的元素會變化,如果 allPath 直接新增 curPath(allPath.add(curPath)),那麼 allPath 中的元素也會隨著遞迴的出棧而變化
所以這兩個注意點可以歸納為一點:為什麼要回溯
不理解為什麼要回溯的小夥伴,可以先去查查回溯的相關資料
在這裡,回溯的作用是還原現場,保證我們能夠正確的找到所有路徑
摺紙問題
把紙條豎著放在桌⼦上,然後從紙條的下邊向上⽅對摺,壓出摺痕後再展開,此時有 1 條摺痕,突起的方向指向紙條的背⾯,這條摺痕叫做“凹”摺痕 ;突起的⽅向指向紙條正⾯的摺痕叫做“凸”摺痕
如果每次都從下邊向上方對摺,對摺 N 次,請從上到下列印出所有摺痕的方向
我們用紙條去實操下,就會發現規律,這就是一個二叉樹的中序遍歷(嚴格來時,是滿二叉樹的中序遍歷)
很簡單,直接看程式碼
這題很容易,只要你去實操摺紙,找到了規律,程式碼實現就是手到擒來
最低公共祖先
求同一棵二叉樹中兩個節點的最低公共祖先節點
什麼是最低公共祖先,節點往上向根節點移動,兩個節點最先匯聚的節點則是這兩個節點的最低公共祖先,例如
10 和 4 的最低公共祖先就是 3
簡單的做法是藉助雜湊表
先遍歷一次二叉樹,記錄所有節點的父節點(HashMap),然後找出其中某個節點(n1)的所有祖先節點(存放到 HashSet 中)
再從另一個節點(n2)開始,從 HashMap 中逐個找 n2 的祖先節點的同時,判斷 n2 的當前祖先節點是否在 HashSet 中
一旦找到,這直接返回該節點,就是 n1 與 n2 的最低公共祖先
我們來看程式碼
很好理解,也很好實現,就是有點費空間
還有一種方式,實現非常簡單,但卻不好理解,是前輩們反覆提煉之後得到的一種解法
大家千萬不要只盯著程式碼看,需要結合具體的二叉樹結構、n1與n2的關係,逐行程式碼去模擬,去找感覺,來理解這種方式
這可是前輩們反覆提煉之後的方法,如果你一眼就看懂了,那豈不是太過分了?
總結
1、二叉樹的遍歷是解二叉樹題的基礎,基礎一定要打牢
相信大家已從上述幾個案例中感覺到了
基礎不牢,地動山搖,你們懂的
2、舉的案例有限,目的也僅僅只是給大家找找感覺
有了一定的感覺,就可以力扣走起:二叉樹