leetcode - 二叉樹

前序/中序/後序/層序遍歷(非遞歸版本)

遞歸版本見註釋部分代碼

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

迭代方法使用棧或者隊列。當前訪問的節點一定是棧頂的節點(都是先將根節點入棧),先取出,再按照遍歷順序將其他節點入棧。

①前序遍歷

這個方法是先讀取根節點結果,右子樹、左子樹分別入棧,出棧輸出結果則是根->左->右

class Solution:
    # def __init__(self):
    #     self.res = []
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 遞歸
        #  if root: 
        #     self.res.append(root.val)
        #     self.preorderTraversal(root.left)
        #     self.preorderTraversal(root.right)
        # return self.res
            
        # 棧
        res = []
        if not root: return res
        s = [root]
        while s:
            node = s.pop()
            res.append(node.val)
            if node.right: s.append(node.right)
            if node.left: s.append(node.left)            
        return res

② 中序遍歷
只對根節點做出入棧的處理。根先入棧,遍歷左子樹,根出棧,遍歷右子樹,則輸出結果爲左->根->右。

class Solution:
    # def __init__(self):
    #     self.res = []
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        # 遞歸
        # if root:
        #     self.inorderTraversal(root.left)
        #     self.res.append(root.val)
        #     self.inorderTraversal(root.right)
        # return self.res
        
        # 棧
        res = [] 
        if not root :return res
        s = []
        while s or root:
            if root:
                s.append(root)
                root = root.left
            else:
                root = s.pop()
                res.append(root.val)
                root = root.right
        return res

③後序遍歷

巧妙借用前序的思想,先讀取根節點結果,左子樹、右子樹依次入棧,則輸出爲根->右->左,最後將輸出逆序就能生成正確的後序輸出:左->右->根。

class Solution:
    # def __init__(self):
    #     self.res = []
    def postorderTraversal(self, root):
        # 遞歸
        # if root:
        #     self.postorderTraversal(root.left)
        #     self.postorderTraversal(root.right)
        #     self.res.append(root.val)
        # return self.res
        
        # 棧
        res = []
        if not root: return res
        s = [root]
        while s:
            node = s.pop()
            res.append(node.val)
            if node.left: s.append(node.left)
            if node.right: s.append(node.right)
        return res[::-1]

還有一種更容易理解的方法:“顏色標記法”,兼具棧迭代方法的高效,又像遞歸方法一樣簡潔易懂,更重要的是,這種方法對於前序、中序、後序遍歷,能夠寫出完全一致的代碼。

其核心思想如下:

  • 使用顏色標記節點的狀態,新節點爲白色,已訪問的節點爲灰色。
  • 如果遇到的節點爲白色,則將其標記爲灰色,然後將其右子節點、自身、左子節點依次入棧。
  • 如果遇到的節點爲灰色,則將節點的值輸出
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        WHITE, GRAY = 0, 1
        stack, res = [(WHITE, root)], []
        while stack:
            color, node = stack.pop()  
            if node is None:
                continue
            if color == WHITE:
                stack.append((GRAY, node)) #前序/中序/後序調整下入棧的順序就可以。因爲出棧順序跟入棧的相反,而前中後都是先左後右,只是根的位置不同,所以右都比左先入棧。這行放最前就是後序,放中間就是中序,放最後就是前序。
                stack.append((WHITE, node.right))
                stack.append((WHITE,node.left))  
            else:
                res.append(node.val)
        return res 

④ 層序遍歷

用隊列的方法,每次訪問隊首節點,並將其左、右子樹入隊。

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        q, res = [root], []
        while q:
            level = []
            tmp_q, q = q, []
            for node in tmp_q:
                if node:
                    level.append(node.val)
                    q.append(node.left)
                    q.append(node.right)
            if level:
                res.append(level)
        return res

2)遞歸方法中的自頂向下 / 自底向上

舉例:求二叉樹最大深度

① 自頂向下( 通過函數傳值的方法,從頂層傳到底層)

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        return self.dfs(root,0)
    def dfs(self, root, depth):
        if not root: return depth
        l_depth = self.dfs(root.left,depth+1)
        r_depth = self.dfs(root.right,depth+1)
        return max(l_depth,r_depth)

② 自底向上

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root: return 0
        l_depth = self.maxDepth(root.left)
        r_depth = self.maxDepth(root.right)
        return max(l_depth,r_depth)+1

練習:

1)對稱二叉樹
思路:

  • 它們的兩個根結點具有相同的值;
  • 每個樹的右子樹都與另一個樹的左子樹鏡像對稱。
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        return self.dfs(root.left,root.right)
        
    def dfs(self,node1,node2):
        if node1 == None and node2 == None : #左、右都爲葉子節點
            return True
        elif node1 == None :  #左、右僅一方爲葉子節點
            return False
        elif node2 == None:
            return False
        else:
            return (node1.val == node2.val) \
                    and self.dfs(node1.left,node2.right) \
                    and self.dfs(node1.right,node2.left)
        

2)路徑總和
給定一個二叉樹和一個目標和,判斷該樹中是否存在根節點到葉子節點的路徑,這條路徑上所有節點值相加等於目標和。

思路:自頂向下,向葉子節點傳值

class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if not root:
            return False
        sum -= root.val
        if not root.left and not root.right: # 葉子節點計算結果
            return sum == 0  
        return self.hasPathSum(root.left,sum) \
               or self.hasPathSum(root.right,sum)  #自頂向下,向葉子節點傳值

3)從中序與後序遍歷序列構造二叉樹
在這裏插入圖片描述
注:題目規定,二叉樹中沒有重複的元素。
思路:

  • 後序遍歷最後一個爲root節點
  • 根據root節點在中序遍歷的位置i,可以確定:
    中序遍歷中,inorder[:i]爲左子樹, inorder[i+1:]爲右子樹;
    後序遍歷中,postorder[:i]爲左子樹,postorder[i:len(postorder)-1]爲右子樹。
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        if not inorder or not postorder: 
            return None
        root_val = postorder[-1] # 後序遍歷最後一個爲root
        root, root_inorder_idx = TreeNode(root_val), inorder.index(root_val)
        root.left = self.buildTree(inorder[:root_inorder_idx],postorder[:root_inorder_idx])
        root.right = self.buildTree(inorder[root_inorder_idx+1:],postorder[root_inorder_idx:len(postorder)-1])
        return root 

4)從前序與中序遍歷序列構造二叉樹
在這裏插入圖片描述
思路:
前序遍歷第一個爲root節點。
根據root節點在中序遍歷的位置i,可以確定:
中序遍歷中,inorder[:i]爲左子樹, inorder[i+1:]爲右子樹
前序遍歷中,preorder[1:i+1]爲左子樹,preorder[i+1:]爲右子樹。

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not inorder or not preorder: 
            return None
        root_val = preorder[0] # 前序遍歷第一個爲root
        root, root_inorder_idx = TreeNode(root_val), inorder.index(root_val)
        root.left = self.buildTree(preorder[1:root_inorder_idx+1],inorder[:root_inorder_idx])
        root.right = self.buildTree(preorder[root_inorder_idx+1:],inorder[root_inorder_idx+1:])
        return root

5)填充每個節點的下一個右側節點指針(滿二叉樹)
填充它的每個 next 指針,讓這個指針指向其下一個右側節點。如果找不到下一個右側節點,則將 next 指針設置爲 NULL。初始狀態下,所有 next 指針都被設置爲 NULL。
在這裏插入圖片描述
方法1:層次遍歷,無論二叉樹怎麼變化都通用,空間複雜度O(n)。

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root: return None
        q, res = [root], []
        while q:
            level = []
            len_q = len(q)
            for i, node in enumerate(q):
                if i == len_q-1:
                    node.next = None
                else:
                    node.next = q[i+1]
            tmp_q, q = q, []  #更新下一層隊列
            for node in tmp_q:
                if node.left:  #確保隊列中是非空節點
                    q.append(node.left)
                if node.right:
                    q.append(node.right)
        return root

方法2:觀察到滿二叉樹的next是固定的,即left.next是root.right,right.next是root.next.left。(root表示爲父節點)

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return None
        if root.left:
            root.left.next = root.right
        if root.right and root.next:
            root.right.next = root.next.left 
        self.connect(root.left)
        self.connect(root.right)
        return root

6)填充每個節點的下一個右側節點指針II(非滿二叉樹)
在這裏插入圖片描述
和滿二叉樹不同的是,next不是固定的:

  • left.next 可能是root.right,或者需要不斷地從root.next按從左到右的順序查找。
  • right.next同樣,需要不斷地從root.next按從左到右的順序查找。
class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root: return None
        if root.left:  
            if root.right:
                root.left.next = root.right
            else:
                root.left.next = self.look_for_father_next(root)
        if root.right:
            root.right.next = self.look_for_father_next(root)
        self.connect(root.right)  # 因爲需要查找父節點的next,遞歸順序先右再左
        self.connect(root.left)
        return root
    def look_for_father_next(self, root):
        if not root.next:
            return None
        while root.next:  #不斷地從父節點的next節點按從左到右的順序查找
            if root.next.left:
                return root.next.left
            if root.next.right:
                return root.next.right
            root = root.next
        return None

7) 二叉樹的最近公共祖先
在這裏插入圖片描述
思路:
由於每個節點只有唯一一個父節點,我們可以使用到字典的value-key的形式(節點-父節點)字典中預置根節點的父節點爲None。

字典建立完成後,二叉樹就可以看成一個所有節點都將最終指向根節點的鏈表了。於是在二叉樹中尋找兩個節點的最小公共節點就相當於,在一個鏈表中尋找他們相遇的節點。(參考leetcode 160.相交鏈表)

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        father_dict = {root:None}
        def dfs(node):
            if node:
                if node.left:
                    father_dict[node.left] = node
                if node.right:
                    father_dict[node.right] = node
                dfs(node.left)
                dfs(node.right)
        dfs(root)
        l1, l2 = p, q
        while l1 != l2 :  #於是在二叉樹中尋找兩個節點的最小公共節點就相當於,在一個鏈表中尋找他們相遇的節點(假定最近公共祖先存在)
            l1 = father_dict.get(l1, q) 
            l2 = father_dict.get(l2, p)
        return l1

8)二叉樹的序列化與反序列化
在這裏插入圖片描述
思路:
題目要求對層次遍歷做序列化和反序列化,可以藉助兩個隊列,一個保存父節點,一個保存孩子節點,一層層更新。

class Codec:
    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        q, res = [root], []
        if not root: 
            return res
        while q:
            tmp_q, q = q, []
            for node in tmp_q:
                if node:
                    res.append(str(node.val))
                    q.append(node.left)
                    q.append(node.right)
                else:
                    res.append('null')
        while res[-1] == 'null':  #刪除末尾的null
            res.pop()
        return ','.join(res)    #題目要求返回str

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        if type(data) == str:
            data = data.split(',')

        length = len(data)
        if not length:
            return None
        root = TreeNode(data[0])
        parents, childs = [root], []
        i = 1  # 孩子結點在data中的索引
        while i < length:
            for parent in parents:
                if i < length and data[i]:  #更新左子樹
                    node = TreeNode(data[i])
                    parent.left = node
                    childs.append(node)
                i += 1
                if i < length and data[i]:  #更新右子樹
                    node = TreeNode(data[i])
                    parent.right = node
                    childs.append(node)
                i += 1
            if childs:    # 更新下一層,將childs作爲新的fathers
                parents = childs
                childs = []
        return root
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章