O-07 重建二叉树
题目
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
给出:
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回:
3
/ \
9 20
/ \
15 7
分析与代码
前序遍历性质: 节点按照 [ 根节点 | 左子树 | 右子树 ]
排序。
中序遍历性质: 节点按照 [ 左子树 | 根节点 | 右子树 ]
排序。
对于示例树,
前序遍历划分 [ 3 | 9 | 20 15 7 ]
中序遍历划分 [ 9 | 3 | 15 20 7 ]
可以直接根据递归来做。
方法1:
ind 可表示左子树的个数也可以表示在中序排列时候根节点的位置,以此来确定递归的index
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if len(preorder)==0:
return None
root=TreeNode(preorder[0])
ind=inorder.index(preorder[0])
#构建左/右子树
left=self.buildTree(preorder[1:ind+1],inorder[:ind])
right=self.buildTree(preorder[ind+1:],inorder[ind+1:])
root.left=left
root.right=right
return root
方法2
参考链接:link
通过以上三步,可确定 三个节点 :1.树的根节点、2.左子树根节点、3.右子树根节点。
对于树的左、右子树,仍可使用以上步骤划分子树的左右子树。
分治算法解析:
-
递推参数: 根节点在前序遍历的索引 root 、子树在中序遍历的左边界 left 、子树在中序遍历的右边界 right ;
-
终止条件: 当 left > right ,代表已经越过叶节点,此时返回 nullnull ;
-
递推工作:
-
建立根节点 node : 节点值为 preorder[root] ;
-
划分左右子树: 查找根节点在中序遍历 inorder 中的索引 i ;为了提升效率,本文使用哈希表 dic 存储中序遍历的值与索引的映射,查找操作的时间复杂度为 O(1)
-
构建左右子树: 开启左右子树递归;
-
返回值: 回溯返回 node ,作为上一层递归中根节点的左 / 右子节点
难理解的几点:
1.“in_left”意义是当前树的左边界,“in_right”即是右边界,即inorder_left_bound,inorder_right_bound。
2.“i”是根节点在中序遍历中的坐标,即"inorder_root"。
3.“i-in_left+preroot+1”
,其实就是右子树根节点=(中序根节点坐标-中序左边界)+先序根节点坐标+1,其中括号内=左子树长度。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
def recur(root, left, right):
if left > right: return # 递归终止
node = TreeNode(preorder[root]) # 建立根节点
i = dic[preorder[root]] # 划分根节点左子树右子树
node.left = recur(root + 1, left, i - 1) # 开启左子树递归
node.right = recur(i - left + root + 1, i + 1, right) # 开启右子树递归
return node # 回溯返回根节点
dic, preorder = {}, preorder
for i in range(len(inorder)):
dic[inorder[i]] = i
return recur(0, 0, len(inorder) - 1)
O-26 树的子结构
题目
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
分析与代码
参考链接:匹配类二叉树算法总结
这类题目与字符串匹配有些神似,求解过程大致分为两步:
先将根节点匹配;
根节点匹配后,对子树进行匹配。
而参与匹配的二叉树可以是一棵,与自身匹配;也可以是两棵,即互相匹配。
比如 对称二叉树 就是两棵树之间的匹配问题。
求解思路可以分解为以下两步:
匹配根节点:首先在 A 中找到与 B 的根节点匹配的节点 C;
匹配其他节点:验证 C 的子树与 B 的子树是否匹配。
代码中主要涉及 主函数和 dfs 函数 两个部分。与以上思路对应,主函数对应根节点的匹配,dfs 函数对应匹配其他节点。
dfs 函数
dfs 函数将注意力集中在了根节点已经匹配的情况。当从根节点同时开始向下遍历时,我们进行以下判断:
如果 A 和 B 同时遍历到了 null,说明匹配成功,返回 True(case 1 红色虚线框);
如果 A 或 B 提前遍历到了 null,一棵树匹配完了,另一棵却没有,说明子树的结构是不同的,则匹配失败,返回 False(case 2 红色虚线框)
以上是“基本情况”,在到达基本情况之前,我们需要判断根节点的值、**左子树(调用递归)和右子树(调用递归)**是否匹配
主函数
现在将视野放远来看,主函数则解决了如何确定 A 的哪个节点是 B 的根节点。
如果 A 的当前节点值与 B 的根节点值相同,我们调用 dfs 函数判断子树是否也相同;如果不同,我们就递归调用主函数来寻找 A 的哪个节点与 B 的根节点匹配。
主函数还经常需要判断以下边界条件,比如如果 A 为空,则肯定不匹配,返回 False。
对于本题来讲,与前面的例题很像,不同的是 B 属于 A 的一部分也可以,没必要一直匹配到叶子节点。因此只需对 check 函树的基本条件进行修改即可
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def check(self,a,b):
#先判断一下,需匹配的子树是否匹配完,匹配完则True
if not b :return True
if not a :return False
return a.val==b.val and self.check(a.left,b.left) and self.check(a.right,b.right)
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
if not A or not B: return False
return self.check(A,B) or self.isSubStructure(A.left,B) or self.isSubStructure(A.right,B)
O-27 二叉树的镜像
题目
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
分析与代码
方法1 递归
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
# 直接交换
if not root:return None
root.left,root.right=self.mirrorTree(root.right),self.mirrorTree(root.left)
return root
方法2 辅助栈
参考链接:link
二叉树镜像定义: 对于二叉树中任意节点 root ,设其左 / 右子节点分别为 left, right ;则在二叉树的镜像中的对应 root节点,其左 / 右子节点分别为 right, left。
利用栈(或队列)遍历树的所有节点 node ,并交换每个 node 的左 / 右子节点。
算法流程:
- 特例处理: 当 root 为空时,直接返回 null ;
- 初始化: 栈(或队列),本文用栈,并加入根节点 root。
- 循环交换: 当栈 stack 为空时跳出;
- 出栈: 记为 node ;
- 添加子节点: 将 node左和右子节点入栈;
- 交换: 交换 node 的左 / 右子节点。
- 返回值: 返回根节点root 。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def mirrorTree(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
O-28 对称的二叉树
题目
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
分析与代码
参考链接:link
思路:
对称二叉树定义: 对于树中 任意两个对称节点 L和 R,一定有:
- L.val = R.val:即此两对称节点值相等。
- L.left.val = R.right.val:即L的左子节点和R的右子节点对称;
- L.right.val = R.left.val:即L的右子节点和R的左子节点对称。
根据以上规律,考虑从顶至底递归,判断每对节点是否对称,从而判断树是否为对称二叉树
isSymmetric(root) :
特例处理: 若根节点 root 为空,则直接返回 true 。
返回值: 即 recur(root.left, root.right) ;
recur(L, R) :
- 终止条件:
当 L和 R同时越过叶节点: 此树从顶至底的节点都对称,因此返回 true ;
当 L或 R只有一个越过叶节点: 此树不对称,因此返回 false ;
当节点 L值! =节点 R 值: 此树不对称,因此返回 false ; - 递推工作:
判断两节点L.left 和R.right 是否对称,即recur(L.left, R.right) ;
判断两节点 L.right 和 R.left 是否对称,即 recur(L.right, R.left) ;
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def recur(left,right):
if not left and not right: return True
if not left or not right or left.val!=right.val:return False
return recur(left.left,right.right)and recur(left.right,right.left)
return recur(root.left,root.right) if root else True
—2020/01/15–二叉树/递归----