Day14 二叉樹Part2 遞迴的應用(二叉樹相關)

haohaoscnblogs發表於2024-07-30

任務

226. 翻轉二叉樹

給你一棵二叉樹的根節點 root ,翻轉這棵二叉樹,並返回其根節點。

思路

邏輯上,我們知道用遞迴遍歷一棵樹,一定會遍歷每個節點。因此在遍歷的過程中處理即可。

  • 考慮遞迴基,即當節點為空時直接返回。
  • 考慮單層遞迴,為了反轉二叉樹,如何處理當前節點呢?即如何反轉以當前節點為根的二叉樹呢? 交換其左右子節點,然後分別以左右孩子節點為根去反轉(遞迴處理孩子節點)。

實際上這裡的思路我們使用正好使用了類似先序遍歷的流程,先處理當前節點的區域性,再遞迴處理其孩子節點;實際上,如果在這裡採用後序遍歷的思路,也是可以的,也即是先讓孩子節點為根去反轉,再將自己的左右孩子反轉。

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root: return root
        tmp = root.left
        root.left = root.right
        root.right = tmp
        
        self.invertTree(root.left)
        self.invertTree(root.right)
        return root

特別地,如果採用中序遍歷,同樣可以實現功能,但是相對於前後序需要詳細思考下,不能無腦的認為呼叫遞迴函式就是呼叫左右子節點的遞迴函式,而是要用單層遞迴的實際思路,先以左孩子為根去反轉,再交換左右孩子,此時就需要再以左孩子為根去反轉了(因為此時左孩子已經是之前的右孩子了)

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root: return root
        self.invertTree(root.left)
        tmp = root.left
        root.left = root.right
        root.right = tmp
        self.invertTree(root.left)
        return root

實際上個人認為不用太區分樹的遍歷用遞迴時用的是什麼序遍歷,而是根據單次遞迴中需要的邏輯是什麼去思考和編碼即可,如果題目夠複雜,你的遍歷可能都不是單純的處理一次,分不出先中後序。即處理時機可能在遞迴呼叫左右子節點的前中後多個部分。

101. 對稱二叉樹

給你一個二叉樹的根節點 root , 檢查它是否軸對稱。

思路

看到這道題,想到要遍歷兩棵樹的過程中,判斷這兩棵樹是否是對稱的。這道題的遞迴基和單次遞迴邏輯聯絡的更緊密一些

  • 如果這兩棵樹的樹根均為空,則返回True
  • 如果這兩樹的樹根不同時為空,則返回False
  • 如果這兩樹的樹根均不為空,判斷如下
    • 如果兩樹根值不等,則返回False
    • 如果兩樹根值相等,則判斷它們的子樹是否為對稱(a的左子樹是否和b的右子樹對稱,a的右子樹是否和左子樹對稱),同時滿足則返回True,否則返回False
class Solution:
    def isSymmetricTwoTree(self,root1,root2):
        if root1 and root2: 
            if root1.val != root2.val:
                return False
            else: # 值相等時,同時遍歷兩棵樹的子樹,確認是否對稱
                case1 = self.isSymmetricTwoTree(root1.left,root2.right)
                case2 = self.isSymmetricTwoTree(root1.right,root2.left)
                return case1 and case2
        elif not root1 and not root2:
            return True
        # elif not root1 and root2 or (root1 and not root2):
        #     return False
        else: return False

    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        return self.isSymmetricTwoTree(root.left,root.right)

104.二叉樹的最大深度

給定一個二叉樹 root ,返回其最大深度。
二叉樹的 最大深度 是指從根節點到最遠葉子節點的最長路徑上的節點數。

思路

  • 遞迴基:當前節點為空時,返回0
  • 單層遞迴邏輯:當前節點的最大深度為,其左節點的最大深度和右節點最大深度中較大的加1.
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root: return 0
        leftNum = self.maxDepth(root.left)
        rightNum = self.maxDepth(root.right)
        return max(rightNum,leftNum) + 1

111. 二叉樹的最小深度

給定一個二叉樹,找出其最小深度。
最小深度是從根節點到最近葉子節點的最短路徑上的節點數量。
說明:葉子節點是指沒有子節點的節點。

思路

大體思路和最大深度相同,但是直接將max改為min後,發現結果出錯,一個只有右鏈的樹返回的值是1,說明單層邏輯考慮的情況節點的左右孩子一邊為空的時候按照之前的邏輯是不對的,因此需要特別處理,當某孩子一邊為空,則返回另一孩子的最小深度。

class Solution:
    def minDepth(self, root: Optional[TreeNode]) -> int:
        if not root: return 0
    
        leftNum = self.minDepth(root.left)
        rightNum = self. minDepth(root.right)
        if not root.left and root.right: return rightNum + 1
        elif root.left and not root.right: return leftNum + 1
        return min(rightNum,leftNum) + 1

注意這最後兩道題,最大深度和最小深度,用之前的層序遍歷也可以實現。

  • 最大深度用層序遍歷的邏輯是,開始用res記錄,每層加1,層序遍歷完成後返回res
  • 最小深度用層序遍歷的邏輯是,用res記錄,同樣每層加1,但是如果提前碰到葉子節點,則直接返回res加1。

也即是說,用dfs和bfs都可以實現這兩個功能,目前在二叉樹章節中的理解是,dfs是用遞迴的邏輯去思考(遞迴基,單層遞迴邏輯),bfs是用層序遍歷邏輯處理,也就是用層序遍歷的那個佇列二重迴圈的模板去處理。

心得體會

對於這種遞迴的題目,思考如何解時不要陷入到整個流程運作的細節,而是思考這樣兩個問題:

  1. 為了讓遞迴不無限下分,邊界條件是什麼?也就是,遞迴基是什麼?有了歸基,我們知道遞迴可以有終點,會一層一層的返回給上層遞迴函式(類比迴圈中的終止條件是什麼),此外,這個相當於我們針對邊界的處理,比如節點為空時我們的題意的邏輯是什麼?
  2. 單層遞迴時,為了實現邏輯,我們該怎麼做?(類比迴圈中的單次迴圈中我們該做什麼) 這個相當於是如果當前節點不是邊界,我們的題意的邏輯是什麼。

相關文章