代码随想录算法训练营 Day 11

3 minute read

Published:

二叉树

常用的二叉树

满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。若一棵满二叉树深度为k,那么其节点数量为$2^k - 1$

完全二叉树:除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若一棵完全二叉树的的最底层为第k层,那么该层节点数量范围[1, $2^{(h-1)}$]

二叉搜素树(有序树):1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;2.若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;3. 它的左、右子树也分别为二叉排序树

平衡二叉搜索树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

存储方式

链式存储 - 用指针指向孩子节点

class TreeNode:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

顺序存储 - 用数组存储;假如一个节点的索引为i,那么其左孩子节点的索引为$i * 2 + 1$,右孩子节点的索引为$i * 2 + 2$

遍历方式

深度优先遍历(DFS) - 递归法,迭代法

前序遍历:父节点 -> 左孩子节点 -> 右孩子节点 中序遍历:左孩子节点 -> 父节点 -> 右孩子节点 后序遍历:左孩子节点 -> 右孩子节点 -> 父节点

注意:左右孩子节点可能为一棵子树,需要再按照同样的顺序进行考虑


广度优先遍历(BFS) - 迭代法

递归遍历

递归三要素:1. 确定递归函数的参数和返回值;2. 确定终止条件;3. 确定单层递归的逻辑

前序遍历

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []

        def dfs(node):
            if not node:
                return
            
            res.append(node.val)
            dfs(node.left)
            dfs(node.right)
        
        dfs(root)

        return res

中序遍历

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []

        def dfs(node):
            if not node:
                return
            
            dfs(node.left)
            res.append(node.val)
            dfs(node.right)
        
        dfs(root)

        return res

后序遍历

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []

        def dfs(node):
            if not node:
                return
            
            dfs(node.left)
            dfs(node.right)
            res.append(node.val)
        
        dfs(root)

        return res

Take some notes

递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中

迭代遍历

利用栈将节点存储起来,然后每次从栈中推出节点

前序遍历

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        stack = [root]
        res = []
        while stack:
            curr = stack.pop()
            res.append(curr.val)
            
            # 栈是先进后出,因此需要先将右节点放入栈中。这样先出栈的就是左节点
            if curr.right:
                stack.append(curr.right)
            
            if curr.left:
                stack.append(curr.left)
        
        return res

中序遍历 (不能先将根节点放入栈中)

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        res = []
        stack = []
        curr = root

        while curr or stack:
            if curr:
                stack.append(curr)
                # 遍历左子树,直到不包含左子树
                curr = curr.left
            else:
                curr = stack.pop()
                res.append(curr.val)
                # 取栈顶节点的右子树
                curr = curr.right
        
        return res

后序遍历

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        stack = [root]
        res = []

        # 先按照中右左的顺序进行遍历,最后将结果翻转
        while stack:
            curr = stack.pop()

            res.append(curr.val)

            if curr.left:
                stack.append(curr.left)
            
            if curr.right:
                stack.append(curr.right)
            
        return res[::-1]

统一遍历

利用标记法,确定节点是否可以被加入到结果中

class Solution:
    def Traversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        
        stack = [root]
        res = []

        while stack:
            curr = stack.pop()

            if curr:
                # 前序遍历
                if curr.right:
                    stack.append(curr.right)
                
                if curr.left:
                    stack.append(curr.left)
                
                stack.append(curr)
                stack.append(None)

                # 中序遍历
                if curr.right:
                    stack.append(curr.right)
                
                stack.append(curr)
                stack.append(None)

                if curr.left:
                    stack.append(curr.left)

                # 后序遍历
                stack.append(curr)
                stack.append(None)
                
                if curr.right:
                    stack.append(curr.right)

                if curr.left:
                    stack.append(curr.left)
            else:
                curr = stack.pop()
                res.append(curr.val)
        
        return res

Take some notes

注意使用栈进行迭代遍历时,入栈的顺序和遍历顺序的关系

层序遍历

Q1. LeetCode 102

解法1

from collections import deque
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        res = []
        if not root:
            return res
        
        queue = deque([root])
        while queue:
            temp = []
            temp_q = []
            for _ in range(len(queue)):
                curr = queue.popleft()
                temp.append(curr.val)
                if curr.left:
                    temp_q.append(curr.left)
                if curr.right:
                    temp_q.append(curr.right)
            
            res.append(temp)
            queue = deque(temp_q)
        
        return res

解法2: 递归法

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        res = []
        if not root:
            return res
        
        def bfs(node, l):
            if not node:
                return
            
            if len(res) == l:
                res.append([])
            
            res[l].append(node.val)
            bfs(node.left, l + 1)
            bfs(node.right, l + 1)
        
        bfs(root, 0)

        return res

Q2. LeetCode 107

将Q1结果进行翻转即可

class Solution:
    def levelOrderBottom(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []
        
        res = []
        queue = deque([root])
        while queue:
            temp = []
            for _ in range(len(queue)):
                curr = queue.popleft()
                temp.append(curr.val)
                if curr.left:
                    queue.append(curr.left)
                if curr.right:
                    queue.append(curr.right)

            res.append(temp)
        return res[::-1]

Q3. LeetCode 199

class Solution:
    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        if not root:
            return res
        
        queue = deque([root])
        while queue:
            for _ in range(len(queue) - 1):
                node = queue.popleft()

                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
                
            
            node = queue.popleft()
            res.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        return res

TODO

层序遍历的其他问题