题目 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 函数,以及为什么你需要在每一层都检查节点是否对称。