跌跌撞撞看程式碼隨想錄看leetcode官方題解,終於寫完了hot100的二叉樹部分。
這是我第一次學習如何正式的用java去寫一個二叉樹
首先在自己的編譯器裡定義一個TreeNode類,以便於後面刷題的時候複用
public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() {} TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; } }
第一題——二叉樹的中序遍歷
沒什麼好說的,入門題目,遞迴一下就有答案了。
前中後序遍歷只需要記住根左右,左根右,左右根即可。
第二題——二叉樹的最大深度
這道題目我使用的是後序遍歷,左右根:取左右的最大層數,根的層數是最大層數+1
第三題——翻轉二叉樹
很明顯需要先對根進行操作,調換左右子樹位置,所以使用的是先序遍歷。
第四題——對稱二叉樹
這道題在做的時候我的思路有一點錯誤:中序遍歷左根右,乍一看示例1我們發現中序遍歷是符合對稱的,其實不然。
對稱二叉樹一定可以中序遍歷對稱;而中序遍歷對稱不一定是對稱二叉樹。
所以我們可以選擇先補全樹:只要樹的左節點和右節點不同時為空,那麼就把他的空節點補為null。
或者另一種做法:因為是滿足對稱,所以我們可以讓左右子樹同時遞迴:左子樹的左節點對應右子樹的右節點;左子樹的右節點對應右子樹的左節點。一旦只有一方為null或者兩值不相等那麼就判斷該樹不對稱
關於返回值:以最簡單的三層樹為例:第三層不相等我們會直接返回false,這時第二層會收到左子樹和右子樹兩個的返回結果,一旦有一個false,那麼第二層也應該返回false,這樣層層返回的遞迴思想。只要有一個false,那麼往上返回的結果就都會是false
第五題——二叉樹的直徑
這道簡單題?自己做的話我是想不出來怎麼做。其實現在回頭一看,他和第二道題有些相似處,可能我當時隔了太久吧
同樣需要後序遍歷:取左子樹和右子樹深度的最大值再+1,作為當前根節點的深度,除此以外我們還需要維護一個最大直徑的全域性變數:直徑是兩個節點之間的長度,從根節點來說,直徑=左子樹深度+右子樹深度
第六題——二叉樹的層序遍歷
這題程式碼隨想錄的方法我記得印象很深,可能是從這裡開始我就開始認真刷題了,也可能是這裡開始我做二叉樹開始變快了。
我們需要用到額外的資料結構來實現層序遍歷:先進先出的佇列
先把根節點扔進佇列裡,然後記錄佇列的長度。接下來把佇列內的treeNode依次丟入陣列,每出一個treeNode就意味著我們要新放入他的子節點,這樣當我們把記錄好的size丟到0時,保證佇列裡的資料是下一層(層序遍歷,很奇妙吧.jpg)然後重複上述記錄佇列長度的操作。
第七題——將有序陣列轉換為二叉搜尋樹
這題同樣是程式碼隨想錄的方法:二叉搜尋樹即左子樹永遠小於根節點,右子樹永遠大於根節點。
題目要求是平衡二叉搜尋樹(即左右子樹深度差不得大於1)。因此最中間的節點肯定就是根節點,而根節點左邊的資料肯定是左子樹,右邊的資料肯定是右子樹。然後以此遞迴(不知道為什麼讓我想起了二分查詢)
第八題——驗證二叉搜尋樹
這道題已經忘記了程式碼隨想錄是怎麼講的了......
看二叉樹的圖的時候我想起來,這題應該用中序遍歷,左根右。第七題提到了什麼是二叉搜尋樹,那麼依照左根右的遍歷順序,我們得到的list一定是依次遞增的。
程式碼隨想錄給的進階方案是:維護一個preTreeNode,他一直是當前節點的前一個節點。我們只需要判斷當前節點是否大於前一個節點即可
第九題——二叉搜尋樹中第K小的元素
依舊是中序遍歷,我們只需要額外維護一個記錄當前是第幾小的元素的全域性變數即可,自己秒了。
第十題——二叉樹的右檢視
這題沒讀懂題啊,什麼弱智慧出出來這種題???而且給的樣例和屎一樣我完全不懂哇!
翻閱評論區大神,原來只需要取出二叉樹最右邊的所有節點即可。
層序遍歷秒了,這裡只留下最後一個節點存入list,其他節點通通拜拜
第十一題——二叉樹展開為連結串列
題目有些難懂,後來知道原來只是把二叉樹先序遍歷的結果寫成連結串列。不過題目中要求的是根節點還是那個根節點。
這裡我的做法是先把二叉樹先序遍歷一下儲存結果,然後再依據結果修改樹:左節點置空,右節點安裝遍歷結果依次插入,這裡要控制好遍歷結果和樹的對應關係:遍歷結果肯定是包含根節點的,因此我們需要讓根節點的右孩子指向i=1的元素
第十二題——從前序和中序遍歷構造二叉樹
這題就是比較難的了,畢竟我學資料結構的時候只能是靠人腦推算,現在讓我寫程式碼,兩眼一閉。
再一次聽程式碼隨想錄講了資料結構的知識,其實聽了一遍感覺簡單了不少:
1.根據前序/後序遍歷,我們可以確定誰是根節點。
2.根據根節點+中序遍歷根節點的位置,我們可以確定根節點的左子樹與右子樹
3.根據中序遍歷我們獲取了左右子樹之後,再回過頭來看前序/後序遍歷,我們就可以找到新的根節點
這裡的難點是陣列切分,根據根節點+中序遍歷我們可以得知左子樹長度和右子樹長度;然後依據長度我們再去前序/後序遍歷尋找子樹的根節點
第十三題——路徑總和III
為了做這道題我把路徑總和I和路徑總和II也給寫了
路徑總和I:這裡使用的是前序遍歷,從根節點出發,每經過一個節點,我們所求的數就減少當前節點的val,當到葉子節點並且所求數減到0,那麼說明這條路徑就是我們需要的路徑
路徑總和II:這道題對我來說很難,因為程式碼隨想錄沒講.....我遇到的難點是我只能存下來一個list陣列,而且還是倒置的,倒置問題可以用Collections.reverse解決,存下一個陣列這種複製問題我不會解決,
後看官方題解:不需要使用list,轉為使用deque,這樣就不需要解決倒置問題。每經過一個節點就把他offer進deque中,當我們遞迴返回的時候只需要把他poll出去即可,這樣按照總和I的解決方案即可求出所有具體路徑。因為要求同樣是葉子節點,所以最後的判定條件是左右孩子都為空且所求數減少到0。(感覺這道題是我上一道沒有完全聽懂程式碼隨想錄方案導致做不出來,做完之後對回溯又有了進一步理解)
路徑總和III:直接變態難,這回不規定是不是根節點,葉子節點,只要是從上往下的和為所求值即可,我直接懵逼!後看官方題解了解大意最終AC了題目:我們把所有節點都看為根節點,像總和I一樣向下延伸看能否得到一個和,只要得到了一個可用值就記錄。當最開始的根節點遍歷結束後,我們再對他的左右子樹進行同樣的做法。也就是說其實這裡是遍歷了兩次樹,第一次遍歷是為了找我們需要的根節點,第二次遍歷才是找我們需要的總和值。
第十四題——二叉樹的最近公共祖先
一個很繞的題,這題我看了別人的解法大概自己寫出來了。
這題只能使用後序遍歷,因為需要左右孩子的資料來推導根節點,題目中說了不會有重複的val,所以我們設定了一個狀態:一旦左右孩子有一個是我們需要的節點,那麼從他開始(他本身也算)都會向上返回一個標誌,如果兩個標誌相遇了,那麼他們相遇的treeNode就是他們的祖先。其實這題還耗費了我不少時間:我只考慮了左右孩子是我們需要的節點的時候向上傳遞標誌,後面他們的父節點也要攜帶標誌向上傳遞我沒有實現。
第十五題——二叉樹中的路徑最大和
這題和路徑總和系列不太像,和求二叉樹的直徑問題有些相似
這道題難在我們需要記錄的東西比較多:路徑最大和需要記錄,某一節點向下延伸的最大值我們也需要記錄,很繞。
我們在記錄某一節點的最大值時,需要比較他的左右子樹誰大,然後加到當前節點的數值,是兩個運算元。是樹形DP。而我們需要求的最大值,他卻是三個運算元:左子樹+右子樹+自身,而非左子樹或右子樹+自身