这一节,我们看一下应用二叉树的遍历,可以解决哪些问题,如何应用这些原理刷题。花式遍历,加上我们接下来解析的题目,可以基本涵盖数据结构二叉树这部分的主要内容了。还是要对其中难点好好练习。
其实,在LeetCode中有几道题目就是遍历二叉树, 如
94 |
144 |
145 |
102 |
107 |
说明我们上一节其实也是在解析LeetCode的题目哈 →. →
下面我们看教材上的一些经典问题:
1)输出二叉树的所有叶子结点;
先序遍历,判断一下是否叶子。
2)把二叉树T1复制到二叉树T2中;
递归解决。
3)把一个二叉树的左右子树进行交换,不破坏原二叉树;
同上
4)求二叉树中值为x的结点所在层次;
层次遍历,每层计数;或者递归调用函数,递归一次加一层。
5)输出一个二叉树从根结点到每个叶子结点的逆路径;
上一节有个问题,就是每种遍历蕴含哪些信息,其中最重要的一点就是后序遍历过程中,当访问某个元素时,栈中元素就是该元素的所有祖先,即路径。在这一类问题上,遍历时我们需要关注遍历过程中栈内元素。后面我们会对路径问题展开解析。
对于后序遍历,遍历过程中除了包含结点的访问顺序,还有访问该结点时该结点栈中元素等“半路”信息,这种信息十分重要(后序遍历是二叉树路径问题的关键),要学会方法一;方法二中stack2只剩下后序遍历的结点的顺序了,不含有任何中间过程。
6)求二叉树的宽度
层次遍历,比较每层队列的长度即可。
7)二叉树的构造
需要重点提的是二叉树的构造:
这里面竟然还涉及两个定理,总结一句就是 n个不同结点的二叉树,可由中序序列先序序列 或 中序序列和后序序列 唯一的确定。
显然,单一的一个遍历序列,或者先序和后序序列,都无法确定2个结点及以上的二叉树。二叉树的构造,就是根据 中序序列和先序序列 或 中序序列和后序序列 构建一棵二叉树。这也是LeetCode上面的原题。我们后面进行解析。
以上是教材中的重要题目及简单讲解,下面我们对LeetCode中题目归类汇总,讲解二叉树这部分的题目。
(一) 二叉树遍历简单应用
简单先序遍历无难度的几道题。基本都是一遍过。
100. Same Tree
Given two binary trees, write a function to check if they are the same or not.
Two binary trees are considered the same if they are structurally identical and the nodes have the same value.
题目解析:
写了一个递归的方法,对于p和q ,if判断语句是判断两个结点为空是否一致,都不为空,再按结点值-左子树-右子树的顺序依次判断。
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if p and q:
if p.val != q.val:
return False
if not self.isSameTree(p.left, q.left):
return False
if not self.isSameTree(p.right, q.right):
return False
elif p and not q or q and not p:
return False
return True
101. Symmetric Tree
Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).
For example, this binary tree [1,2,2,3,4,4,3]
is symmetric:
题目解析:
这一问题,对称的话一定是根结点为对称轴,然后递归判断左右子树是否对称。写了一个函数,递归判断pq结点值-p左及q右-p右及q左。
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
# 通过中序遍历序列结果来判断是错误的
p = root.left
q = root.right
def symm(p, q):
if not p and q or not q and p:
return False
if p and q:
if p.val != q.val:
return False
s = symm(p.left, q.right)
if not s:
return s
s = symm(p.right, q.left)
if not s:
return s
return True
return symm(p, q)
111. Minimum Depth of Binary Tree
Given a binary tree, find its minimum depth.
The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.
Note: A leaf is a node with no children.
题目解析:
树高度问题的变种,求高度最小的叶子的结点的高度。毫无压力,看代码。
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root:
return 0
h1, h2 = 0, 0
if not root.left and not root.right:
return 1
if root.left:
h1 = self.minDepth(root.left)
if root.right:
h2 = self.minDepth(root.right)
if h1 and not h2:
return h1 + 1
elif h2 and not h1:
return h2 + 1
else:
return min(h1, h2) + 1
222. Count Complete Tree Nodes
Given a complete binary tree, count the number of nodes.
Note:
Definition of a complete binary tree from Wikipedia:
In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h.
题目解析:
题目不难,题目中给出了最核心的思路。第一种方法是先序遍历一遍计算结点数目,明显忽略了完全二叉树的特性,不过性能还不错。但是,在非数组存储结构中,我们如何应用完全二叉树的性质呢?方法二,根据树的深度来计算结点数目。性质1:完全二叉树的深度计算,看最左的叶子即可。 性质2:当左子树与右子树深度相同时,左子树一定是满二叉,因此用2**h直接计算,然后再递归解决右子树;当左右不同时,右子树少一层,一定是满树,左子树不一定满,递归计算。
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
leftDepth = self.getDepth(root.left)
rightDepth = self.getDepth(root.right)
if leftDepth == rightDepth:
return pow(2, leftDepth) + self.countNodes(root.right)
else:
return pow(2, rightDepth) + self.countNodes(root.left)
def getDepth(self, root):
if not root:
return 0
return 1 + self.getDepth(root.left)
都属于极简单的题目,解决方法不唯一,但是思路基本是这样的。
(二)二叉树的构造
我们再来看一下二叉树构造的两个题目。
105 | Construct Binary Tree from Preorder and Inorder Traversal |
106 | Construct Binary Tree from Inorder and Postorder Traversal |
题目解析:
以先序序列和中序序列进行二叉树构造为例,总体思路其实很简单,先序序列确定根结点,根结点在中序遍历中将左右子树分开,于是有了根节点左、右子树的先序序列和中序序列,这样我们就递归的得到了每个子树的先序序列和中序序列。该问题用递归解法十分简洁易懂,代码如下。
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
l = len(preorder)
if not l:
return None
root = TreeNode(preorder[0])
for ix, val in enumerate(inorder):
if val == preorder[0]:
break # 代表左子树结点数为ix个
# ix = inorder.index(preorder[0]) # 这一行代码代替上面遍历的代码,提高很高效率
# rt_len = l-ix-1 # 右子树结点数, 用不到
root.left = self.buildTree(preorder[1:ix+1], inorder[:ix])
root.right = self.buildTree(preorder[ix+1:], inorder[ix+1:])
return root
仅仅几行,这是教材中代码的python实现,但是在LeetCode中如果你关注代码的性能,你会看到这种方法,耗时垫底。那么一定还有更快更好的方法咯~
看下面的代码,抄袭一位大神的结果,耗时和内存都很不错的,其实解题思路并没有变,思考一下上面方法耗时耗内存的原因,莫过于列表切片操作吧,下面的代码,另写一个函数,传入列表中的相应索引,没有切片拷贝的过程,想必优秀在这里吧。
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
def buildNode(i, j):
# base case
if i == j: return None
parent = TreeNode(preorder.pop(0))
ix = inorder.index(parent.val)
parent.left = buildNode(i, ix)
parent.right = buildNode(ix + 1, j)
return parent
return buildNode(0, len(preorder))
对于二叉树的构造,如果还有什么高见也欢迎大家指教哈。
这一节简单介绍二叉树遍历的常见问题,以及LeetCode中比较简单的一些题目,更难的内容还要看后面啦。