102.二叉树的层序遍历
问题
题目链接:https://leetcode.cn/problems/binary-tree-level-order-traversal/
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
学会二叉树的层序遍历,可以一口气打完以下十题:
102.二叉树的层序遍历
107.二叉树的层次遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的层序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
104.二叉树的最大深度
111.二叉树的最小深度
解答
借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。每一层节点出队列的时候同时将下一层的该节点的所有左右节点入队列。
代码:
# 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 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
递归法
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)
该题有一个变形:107.二叉树的层次遍历 II。给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)。只需要将102结果reverse一下就行。
226.翻转二叉树
问题
题目链接:https://leetcode.cn/problems/invert-binary-tree/
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
示例:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
解答
题目比较简单,只需要将每个节点的左右子节点交换一下就行。此题前序后序和层序遍历都可以,但是不能使用中序,因为中序遍历会把某些节点的左右孩子翻转两次。只需要在处理根节点时改成交换左右孩子就行。
层序遍历:
# 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 invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return root
queue = collections.deque([root])
while queue:
for _ in range(len(queue)):
cur = queue.popleft()
left_ = cur.left
cur.left = cur.right
cur.right = left_
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
return root
前序遍历:递归法:
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
root.left, root.right = root.right, root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
前序遍历:迭代法:
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
stack = [root]
while stack:
node = stack.pop()
node.left, node.right = node.right, node.left
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
return root
后序遍历:递归法:
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
self.invertTree(root.left)
self.invertTree(root.right)
root.left, root.right = root.right, root.left
return root
后序遍历:迭代法:
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
stack = [root]
while stack:
node = stack.pop()
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
node.left, node.right = node.right, node.left
return root
严格来说此处并不是中序遍历,因为实际的访问顺序和处理节点顺序是不一样的,虽然是先访问了左节点,但实际先处理的是根节点,不过依然能够每个元素有且只有一次访问,因此采用在前序基础上改变代码顺序就行。还有一点,因为交换左右节点放在了向栈内添加左右节点中间,因此写代码时,如果上一个添加left,下一个添加还是left,因为在中间交换了left和right,此时right变成了left。
中序遍历:递归法:
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
self.invertTree(root.left)
root.left, root.right = root.right, root.left
self.invertTree(root.left)
return root
中序遍历:迭代法:
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
stack = [root]
while stack:
node = stack.pop()
if node.left:
stack.append(node.left)
node.left, node.right = node.right, node.left
if node.left:
stack.append(node.left)
return root
注:经过实验,实际上此处的迭代法本质上都是前序遍历,因为最先处理和访问的都是根节点
101.对称二叉树
问题
题目链接:https://leetcode.cn/problems/symmetric-tree/
给你一个二叉树的根节点 root , 检查它是否轴对称
示例:
输入:root = [1,2,2,3,4,4,3]
输出:true
输入:root = [1,2,2,null,3,null,3]
输出:false
解答
如果使用层序遍历的话思路很简单,判断每一侧是否轴对称就行,因此采用层序遍历,然后比较每一层是否对称就行,但是要注意,因为不一定是完全二叉树,因此当某节点不存在左或右子节点时,需要在结果里添加’null’。
但该方法有个问题,当运行到最下面的叶子节点,也会添加一个’null’,造成最后一层全是null,但比较时这层null也会比较,这实际是没有必要的,因此会有浪费。
# 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:
queue = collections.deque([root])
while queue:
level = []
for _ in range(len(queue)):
cur = queue.popleft()
if cur.left:
queue.append(cur.left)
level.append(cur.left.val)
else:
level.append('null')
if cur.right:
queue.append(cur.right)
level.append(cur.right.val)
else:
level.append('null')
if len(level) % 2 != 0:
return False
else:
i, j = 0, len(level) - 1
while i < len(level)/2:
if level[i] != level[j]:
return False
i += 1
j -= 1
return True
从总体上看,我们要比较的是根节点的左右子树是否相同;因此我们要比较内外节点,对应的就是从左子树的左节点和右子树的右节点,自下而上地比较到左子树的右节点与右子树的左节点。因此左子树的顺序是左右根,右子树的顺序是右左根。这与后序遍历类似。
确定了顺序我们要确定比较的终止条件,由于空子节点的存在,因此可以分为:
- 左右皆空,return true
- 左右只有一边是空,不对称,return false
- 左右都不空,则比较下一级的子树,判断是否相同
注意,虽然说是比较下一级的树是否相同,实际上还是要比较根节点的值是否相同,然后判断根节点的子树是否相同。
最后要确定单层递归的逻辑
单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
如果左右都对称就返回true ,有一侧不对称就返回false 。
递归法:
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
return self.compare(root.left, root.right) #别忘了self.
def compare(self, left, right):
# 判断根节点是否一样,要注意根节点是否为空
if left == None and right == None: return True
elif left == None and right!= None: return False
elif left != None and right == None: return False
# 排除空节点,开始排除根节点值不相同
# 注意判断条件是不等,如果判断的是相等,那么会直接返回True,而不会比较子节点
elif left.val != right.val: return False
# 开始判断子树
outside = self.compare(left.left, right.right)
inside = self.compare(left.right, right.left)
return (outside and inside)
迭代法思路也比较简单,我们分别按照根左右和根右左的顺序输出左右子树,最后比较结果是否相同。但该方法也能改进,即在pop出队列/栈的时候就进行比较,而不用输出最终结果;但此时入队列/栈的顺序就要改变了,因为左子树的左节点要与右子树的右节点相匹配,因此这两个要挨在一起,pop的时候才能同时比较。同理左子树的右节点和右子树的左节点要挨一起。
迭代法:使用队列:
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