剑指 Offer 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
限制:
0 <= 节点个数 <= 10000
/*解题思路:
若树B是树A的子结构,则子结构的根节点可能为树A的任意一个节点。因此,判断树B是否是树A的子结构,需完成以下两步工作:
先序遍历树A中的每个节点nA;(对应函数 isSubStructure(A, B))
判断树A中以nA为根节点的子树是否包含树B。(对应函数 recur(A, B))
算法流程:
名词规定:树A的根节点记作节点A,树B的根节点称为节点B。
recur(A, B) 函数:
终止条件:
当节点B为空:说明树B已匹配完成(越过叶子节点),因此返回true;
当节点A为空:说明已经越过树A叶子节点,即匹配失败,返回false;
当节点A和B的值不同:说明匹配失败,返回false;
返回值:
判断A和B的左子节点是否相等,即 recur(A.left, B.left) ;
判断A和B的右子节点是否相等,即 recur(A.right, B.right) ;
isSubStructure(A, B) 函数:
特例处理:当树A为空或树B为空时,直接返回false ;
返回值:若树B是树A的子结构,则必满足以下三种情况之一,因此用或 || 连接;
以节点A为根节点的子树包含树B,对应 recur(A, B);
树B是树A左子树的子结构,对应 isSubStructure(A.left, B);
树B是树A右子树的子结构,对应 isSubStructure(A.right, B);
以上 2. 3. 实质上是在对树A做先序遍历 。
复杂度分析:
时间复杂度O(MN):其中M,N分别为树A和树B的节点数量;先序遍历树A占用O(M),每次调用recur(A, B)判断占用 O(N)。
空间复杂度O(M):当树A和树B都退化为链表时,递归调用深度最大。当M≤N时,遍历树A与递归判断的总递归深度为M;当 M>N时,最差情况为遍历至树A叶子节点,此时总递归深度为M。
*/
class Method{
public boolean isSubStructure(TreeNode A, TreeNode B) {
return(A!=null&&B!=null)&&(recur(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B));
}
boolean recur(TreeNode A, TreeNode B) {
if(B == null) return true;
if(A == null || A.val != B.val) return false;
return recur(A.left, B.left) && recur(A.right, B.right);
}
}
剑指 Offer 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]
限制:
0 <= 节点个数 <= 1000
二叉树镜像定义: 对于二叉树中任意节点root ,设其左 / 右子节点分别为 left,right ;则在二叉树的镜像中的对应 root 节点,其左 / 右子节点分别为 right,left 。
方法一:递归法
/*
根据二叉树镜像的定义,考虑递归遍历(dfs)二叉树,交换每个节点的左 / 右子节点,即可生成二叉树的镜像。
递归解析:
终止条件:当节点root为空时(即越过叶节点),则返回null;
递推工作:
初始化节点tmp,用于暂存root的左子节点;
开启递归右子节点mirrorTree(root.right),并将返回值作为root的左子节点。
开启递归左子节点mirrorTree(tmp),并将返回值作为root的右子节点 。
返回值:返回当前节点root;
Q:为何需要暂存root 的左子节点?
A:在递归右子节点 “root.left = mirrorTree(root.right);” 执行完毕后,root.leftroot.left的值已经发生改变,此时递归左子节点 mirrorTree(root.left)则会出问题。
复杂度分析:
时间复杂度O(N):其中N为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用O(N)时间。
空间复杂度O(N):最差情况下(当二叉树退化为链表),递归时系统需使用O(N)大小的栈空间。
*/
class Method1{
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
return root;
}
}
方法二:辅助栈(或队列)
/*
利用栈(或队列)遍历树的所有节点node,并交换每个node的左/右子节点。
算法流程:
特例处理:当root为空时,直接返回null ;
初始化:栈(或队列),本文用栈,并加入根节点root 。
循环交换:当栈stack为空时跳出;
出栈:记为node ;
添加子节点:将node左和右子节点入栈;
交换:交换node 的左 / 右子节点。
返回值:返回根节点root 。
复杂度分析:
时间复杂度O(N):其中N为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用O(N)时间。
空间复杂度O(N):如下图所示,最差情况下,栈stack最多同时存储2N+1/2个节点,占用O(N)额外空间。
*/
class Method2{
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
Stack<TreeNode> stack = new Stack<>() {{ add(root); }};
while(!stack.isEmpty()) {
TreeNode node = stack.pop();
if(node.left != null) stack.add(node.left);
if(node.right != null) stack.add(node.right);
TreeNode tmp = node.left;
node.left = node.right;
node.right = tmp;
}
return root;
}
}
总结:
不管是做什么事情都应该静下心来!戒躁戒傲,慢慢的才会发现自己的缺点!然后通过不停的鞭策自己使自身进步!然后更上一层楼!都是一个积累的过程!这个过程可能很艰辛!但是结果却是美好的!
一旦选择好方向就一定要坚持下去!不要轻易言弃!如果觉得累了,可以暂且的停下来思考一番,或许你会找到一个新的方向和新的答案!就像我坚持每天写一篇博客一样!我也不知道会有多少小伙伴去看,但是我依然每天都坚持写!有没有小伙伴看是一回事?写不写又是另外一回事了?渐渐的我的博客多了起来!它也成为了我生活中的一部分!每天更新一篇,如果有事耽误了,我会在后一天及时补上!我只是希望我的博客能给更多的小伙伴带来帮助!渐渐的也担起了一份责任和担当!未来我会一直坚持下去的!
最后,愿我们都能在各行各业中能够取得不同的成就!能够用自身的所学知识为国家贡献出自己的一份力量!一起加油!