Day15 算法学习|二叉树 层序遍历/翻转/对称

题目 102.二叉树的层序遍历



102.二叉树的层序遍历
107.二叉树的层次遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的层序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
104.二叉树的最大深度
111.二叉树的最小深度

解题思路

二叉树的层序遍历的目标是从上到下,一层一层地遍历二叉树的所有节点。我们可以利用队列这种数据结构的特性来实现这个目标。队列的特点是先进先出(FIFO,First In First Out),我们可以把队列想象成一个管道,从一头放入东西,然后从另一头取出。

首先,我们把根节点放入队列。然后,我们进入一个循环,这个循环会持续到队列为空,也就是所有的节点都已经处理过了。在这个循环中,我们首先把队列中的所有节点取出,这些就是当前层级的所有节点。然后,我们把这些节点的值放入一个列表中,并把这些节点的所有子节点放入队列中。这样,队列中就包含了下一层级的所有节点。

整个过程就像是在一层一层地“剥开”二叉树,每次都处理当前层级的所有节点,并准备好处理下一层级的所有节点。

代码

Python代码如下:

import collections

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []
        queue = collections.deque([root])
        result = []
        while queue:
            level = []
            for _ in range(len(queue)):
                cur = queue.popleft()
                level.append(cur.val)
                if cur.left:
                    queue.append(cur.left)
                if cur.right:
                    queue.append(cur.right)
            result.append(level)
        return result 
## 注意事项
在处理二叉树的节点时,我们需要注意以下几点:

我们需要检查节点是否存在。在我们的代码中,如果一个节点不存在(也就是None),我们不会把它放入队列中。
我们需要正确地处理每个节点的子节点。在我们的代码中,我们会把每个节点的左子节点和右子节点放入队列中,但是我们需要确保这些子节点存在。我们使用cur.left和cur.right来访问当前节点的左子节点和右子节点。如果一个子节点不存在,对应的属性就是None。
在Python中,我们使用collections.deque而不是list来实现队列。这是因为deque在插入和删除操作上更高效。在我们这个问题中,使用deque可以保证在每次从队列中取出和放入节点时的时间复杂度都是O(1)## 其他方法
```python
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        levels = []
        self.helper(root, 0, levels)
        return levels
    
    def helper(self, node, level, levels):
        if not node:
            return
        if len(levels) == level:
            levels.append([])
        levels[level].append(node.val)
        self.helper(node.left, level + 1, levels)
        self.helper(node.right, level + 1, levels)

226.翻转二叉树

题目描述

在这里插入图片描述

解题思路

递归法

递归法是一种自上而下的解决方法。对于翻转二叉树,我们可以通过递归地处理每个节点的左右子树来完成整体的翻转。

首先,我们判断当前节点是否为空,如果为空,则返回 None。
如果节点不为空,则交换当前节点的左右子树。
递归地对当前节点的左子树和右子树进行翻转,即调用递归函数 invertTree 分别传入左子树和右子树。
返回当前节点。
递归函数的基准情况(base case)是节点为空时直接返回 None。

代码

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if root is None:
            return []

        # 交换左右子树
        root.left, root.right = root.right, root.left

        # 递归处理左右子树
        self.invertTree(root.left)
        self.invertTree(root.right)

        return root

迭代法

迭代法是一种自下而上的解决方法。对于翻转二叉树,我们使用队列来存储待处理的节点,并按照一定规则进行迭代处理。

首先,我们判断根节点是否为空,如果为空,则直接返回 None。
创建一个队列,并将根节点入队。
进入循环,当队列不为空时:
取出队首节点,交换其左右子树。
如果左子树不为空,则将左子树入队。
如果右子树不为空,则将右子树入队。
返回根节点。

代码

import collections

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if root is None:
            return []

        queue = collections.deque([root])

        while queue:
            node = queue.popleft()
            # 交换左右子树
            node.left, node.right = node.right, node.left

            # 将左右子树入队
            if node.left is not None:
                queue.append(node.left)
            if node.right is not None:
                queue.append(node.right)

        return root

复杂度分析

递归法复杂度分析

时间复杂度:在最坏情况下,我们需要遍历树的所有节点,因此时间复杂度为 O(n),其中 n 是树中节点的数量。

空间复杂度:递归调用的栈空间取决于树的高度,最坏情况下为 O(n),其中 n 是树的高度。但在平均情况下,树的高度较小,因此空间复杂度为 O(log n)。
递归法的优点是实现简单直观,代码清晰易懂。但它可能会占用更多的系统栈空间,当树的高度较大时,可能导致栈溢出。

迭代法复杂度分析

时间复杂度:在最坏情况下,我们需要遍历树的所有节点,因此时间复杂度为 O(n),其中 n 是树中节点的数量。

空间复杂度:迭代法使用了队列来存储待处理的节点,队列中最多可能存储 n/2 个节点,因此空间复杂度为 O(n)。
迭代法的优点是不会占用过多的系统栈空间,因为它不使用递归。它使用队列作为辅助数据结构,可以更好地管理节点的顺序和层级。但相对于递归法,迭代法的实现可能会更复杂一些。

注意

以上的两个方法都是基于前序遍历的方式来翻转二叉树,也可以使用后续遍历,不推荐使用中序遍历。

题目 101. 对称二叉树


问题描述

给定一个二叉树,检查它是否是镜像对称的。
在这里插入图片描述
在这里插入图片描述

解题思路

本题使用递归法来解决问题,后续遍历(左右中),把一个二叉树分为左右两个部分,能同时做到内侧和外侧对称说明这个二叉树才是镜像对称,外侧相等:左节点的外节点(左节点)等于右节点的外节点(右节点); 内侧相等:左节点的内节点(右节点)等于右节点的内节点(左节点)如下图所示: 我们使用递归遍历从root节点开始比较左右节点是否存在并且是否相等,如果存在且相等则继续调用compare函数各自往下递归,知道为空。
请添加图片描述

代码

递归法(重点)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        #如果子树是空的说明对称
        if not root:
            return True
        return self.compare(root.left, root.right)

    def compare(self,left,right):
        #排除空节点(其中一方为空,都为空)
        if left==None and right!=None:
            return False
        elif left!=None and right==None:
            return False
        elif left==None and right==None:
            return True
        #排除不为空节点但是值不同
        elif left.val!=right.val:
            return False
        #当两边相等时,继续往下比较
        outside=self.compare(left.left,right.right)
        inside=self.compare(left.right,right.left)
        isSame=outside and inside
        return isSame
        

迭代法: 使用队列

import collections
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        queue = collections.deque()
        queue.append(root.left) #将左子树头结点加入队列
        queue.append(root.right) #将右子树头结点加入队列
        while queue: #接下来就要判断这这两个树是否相互翻转
            leftNode = queue.popleft()
            rightNode = queue.popleft()
            if not leftNode and not rightNode: #左节点为空、右节点为空,此时说明是对称的
                continue
            
            #左右一个节点不为空,或者都不为空但数值不相同,返回false
            if not leftNode or not rightNode or leftNode.val != rightNode.val:
                return False
            queue.append(leftNode.left) #加入左节点左孩子
            queue.append(rightNode.right) #加入右节点右孩子
            queue.append(leftNode.right) #加入左节点右孩子
            queue.append(rightNode.left) #加入右节点左孩子
        return True

迭代法:使用栈

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        st = [] #这里改成了栈
        st.append(root.left)
        st.append(root.right)
        while st:
            rightNode = st.pop()
            leftNode = st.pop()
            if not leftNode and not rightNode:
                continue
            if not leftNode or not rightNode or leftNode.val != rightNode.val:
                return False
            st.append(leftNode.left)
            st.append(rightNode.right)
            st.append(leftNode.right)
            st.append(rightNode.left)
        return True

层次遍历

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        
        queue = collections.deque([root.left, root.right])
        
        while queue:
            level_size = len(queue)
            
            if level_size % 2 != 0:
                return False
            
            level_vals = []
            for i in range(level_size):
                node = queue.popleft()
                if node:
                    level_vals.append(node.val)
                    queue.append(node.left)
                    queue.append(node.right)
                else:
                    level_vals.append(None)
                    
            if level_vals != level_vals[::-1]:
                return False
            
        return True

复杂度分析

  • 时间复杂度:O(n)
    其中n是树中节点的数量。在最坏的情况下,我们需要检查树中所有的节点一次。所以时间复杂度是线性的。
  • 空间复杂度:O(h),
    其中h是树的高度。递归的深度取决于树的高度。在最坏的情况下(也就是树完全不平衡,像一个链表那样),递归的深度就是树的高度。所以空间复杂度和树的高度相关。

注意事项

  • 空树是对称的
  • 检查空节点
  • 理解递归的工作方式
    • 理解递归是如何在这个问题中工作的非常重要。你需要理解在哪里递归调用 compare 函数,以及为什么你需要在每一层都检查节点是否对称。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值