JZ28 对称的二叉树

(中等)

题目

描述 给定一棵二叉树,判断其是否是自身的镜像(即:是否对称) 例如:下面这棵二叉树是对称的

【算法】解题总结:剑指Offer 28 对称的二叉树、剑指Offer 37 序列化二叉树_算法


下面这棵二叉树不对称。


【算法】解题总结:剑指Offer 28 对称的二叉树、剑指Offer 37 序列化二叉树_序列化_02

数据范围:节点数满足 0 \le n \le 10000≤n≤1000,节点上的值满足 |val| \le 1000∣val∣≤1000


要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)


备注:


你可以用递归和迭代两种方法解决这个问题

示例1
输入:
{1,2,2,3,4,4,3}
返回值:
true

示例2
输入:
{8,6,9,5,7,7,5}
返回值:
false

思路

可采用层序遍历来解决这道题,我的第一想法就是用队列每次存储一层的结点值,然后判断这个序列是否对称即可,也满足空间复杂度为 O(1)常数阶,当然,也可采用递归来解决这道题目,我就是选择的后者,因为我一直感觉我的递归不是很熟练。

我在解决这道题目时,将根结点单独作为一种情况的判断,将其子结点按另一大类情况来判断,但后来又看了一下官方的题解,意识到这两种情况可以合为一种情况,从而简化代码。

递归过程中,传入的参数为两个欲判断是否对称的结点(当然,由于根结点的判断也与后续结点的判断归一化,因此根结点这种情况传两次它的自身引用即可),两结点分三种情况讨论:都为空、一个为空、都不为空

  • 两结点都为空:这种子情况是对称的,返回​​true​
  • 两结点一个为空:一定不对称,返回​​false​
  • 两结点都不为空:要进一步判断是否对称,并且其各自的子结点情况是否对称

实现

public class JZ28对称的二叉树 {
boolean isSymmetrical(TreeNode pRoot) {
return isMirror(pRoot, pRoot);
}

private boolean isMirror(TreeNode node1, TreeNode node2) {
//两结点分三种情况:都为空、一个为空、都不为空

//两结点都为空
if (node1 == null && node2 == null) return true;
//两结点一个为空
if (node1 == null || node2 == null) return false;
//两结点都不为空
return (node1.val == node2.val) &&
isMirror(node1.left, node2.right) &&
isMirror(node1.right, node2.left);
}
}

【算法】解题总结:剑指Offer 28 对称的二叉树、剑指Offer 37 序列化二叉树_算法_03

JZ37 序列化二叉树

(较难)

题目

描述 请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。 二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#) 二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。 例如,可以根据层序遍历的方案序列化,如下图:

【算法】解题总结:剑指Offer 28 对称的二叉树、剑指Offer 37 序列化二叉树_算法_04

层序序列化(即用函数Serialize转化)如上的二叉树转为"{1,2,3,#,#,6,7}",再能够调用反序列化(Deserialize)将"{1,2,3,#,#,6,7}"构造成如上的二叉树。


当然你也可以根据满二叉树结点位置的标号规律来序列化,还可以根据先序遍历和中序遍历的结果来序列化。不对序列化之后的字符串进行约束,所以欢迎各种奇思妙想。


数据范围:节点数 n \le 100n≤100,树上每个节点的值满足 0 \le val \le 1000≤val≤100


要求:序列化和反序列化都是空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

示例1
输入:
{1,2,3,#,#,6,7}
返回值:
{1,2,3,#,#,6,7}
说明:
如题面图

示例2
输入:
{8,6,10,5,7,9,11}
返回值:
{8,6,10,5,7,9,11}

思路

本题分为两问,第一问是将二叉树序列化为字符串,第二问是将字符串反序列化为二叉树。可采用前、中、后、层序遍历来解决,这里,我才用前序遍历来解决,思路大体相同,都是在遍历时生成字符串或生成树。

将二叉树序列化为字符串:可采用递归遍历来实现,这里我采用前序遍历来得出存储各结点值的字符串,各值之间使用英文分号(;)隔开,对于递归时遇到的空结点,用井号(#)表示空结点的值,前序遍历很基础,这里就不再说了。

将字符串反序列化为二叉树:由于此字符串中的值对应二叉树的前序遍历顺序,因此,在反序列化时,也要在递归时进行前序构造二叉树,递归为了方便考虑,可将此字符串中的值以之前分隔符为标识,存储到一个链表中,这样,每次的递归方法栈,以此链表的引用为参数,使得所有的递归都有着统一的生产者源(也就是那个链表),这样,就不必再去考虑用下标索引或是说成员变量式的下标索引来进行字符串中、递归之中下标索引的定位了,从而用前序的构造方式逐步取出链表中的结点值,进而生成新的树结点并插入到相应位置。

关于递归,我一直认为用文字表述不如之间看代码更易懂,因此更建议直接看代码,思路部分只是我自己的一种反思方式。

实现

public class JZ37序列化二叉树 {
String Serialize(TreeNode root) {
if (root == null) return "#;";

return root.val + ";" + Serialize(root.left) + Serialize(root.right);
}

TreeNode Deserialize(String str) {
String[] split = str.split(";");
LinkedList<String> list = new LinkedList();

for (int i = 0; i < split.length; i++) {
list.add(split[i]);
}

return toTree(list);
}

TreeNode toTree(LinkedList<String> list) {
String value = list.poll();

if (value.equals("#")) {
return null;
}

TreeNode root = new TreeNode(Integer.valueOf(value));
root.left = toTree(list);
root.right = toTree(list);
return root;
}
}

【算法】解题总结:剑指Offer 28 对称的二叉树、剑指Offer 37 序列化二叉树_算法_05