一、今日刷题
1.题目
- 二叉树的序列化与反序列化
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
方法签名:
public class Codec {
// 把一棵二叉树序列化成字符串
public String serialize(TreeNode root) {}
// 把字符串反序列化成二叉树
public TreeNode deserialize(String data) {}
}
2.分析
本题分为两部分:序列化 + 反序列化
①序列化就是把结构化的数据「打平」,其实就是在考察二叉树的遍历方式。(递归遍历方式有前序遍历,中序遍历,后序遍历;迭代方式一般是层级遍历。)与一般遍历的不同点就在于,在序列化过程中我们需要将节点为 null 的也要显式的加入到存储序列化结果的字符串中,当然,表示 null 节点的符号是可以自定义的。
②反序列化就是通过二叉树的遍历结果还原一棵二叉树。但之前做过相应的题目,单单前序遍历结果是不能还原二叉树结构的,因为缺少空指针的信息,至少要得到前、中、后序遍历中的两种才能还原二叉树。但是这里的 node 列表包含空指针的信息,所以只使用 node 列表就可以还原二叉树。
3.代码
答案代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// 代表分隔符的字符
String sep = ",";
// 代表 null 空指针的字符
String nullValue = "#";
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
// 用于拼接字符串
StringBuffer sb = new StringBuffer();
serialize(root, sb);
return sb.toString();
}
void serialize(TreeNode root, StringBuffer sb) {
if (root == null) {
sb.append(nullValue).append(sep);
return;
}
sb.append(root.val).append(sep); //丢了append(sep)
serialize(root.left, sb);
serialize(root.right, sb);
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
// 将字符串转化成列表
LinkedList<String> nodes = new LinkedList<>(); //addLast方法是链表特有的,所以没有使用List集合
for (String s : data.split(sep)) {
nodes.addLast(s);
}
return deserialize(nodes);
}
/* 辅助函数,通过 nodes 列表构造二叉树 */
TreeNode deserialize(LinkedList<String> nodes) {
if (nodes.isEmpty()) return null;
/****** 前序遍历位置 ******/
// 列表最左侧就是根节点
String first = nodes.removeFirst();
if (first.equals(nullValue)) return null;
TreeNode root = new TreeNode(Integer.parseInt(first));
/***********************/
root.left = deserialize(nodes);
root.right = deserialize(nodes);
return root;
}
}
// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));
4.知识积累
①String、StringBuffer与StringBuilder之间区别
之前做题时已经遇到过了,这次详细整理一下。
a.关于String与后两者的比较:
String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,这样不仅效率低下,而且大量浪费有限的内存空间,所以经常改变内容的字符串最好不要用 String 。
b.后两者间的比较:
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
c.在做题时还遇到了一个问题,在遍历到一个节点并向 StringBuffer 对象中添加时,使用的语句:
sb.append(root.val).append(sep);
append()方法居然可以添加整型值,查询官方文档得到的信息:
Appends the string representation of the int argument to this sequence.
The overall effect is exactly as if the argument were converted to a string by the method String.valueOf(int), and the characters of that string were then appended to this character sequence.
即当我们像StringBuffer 对象中添加整型值时,整型值被自动转换成 String 型后才被添加。
d.StringBuffer使用实例:
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(1);
stringBuffer.append(", "); //注意这里算两个长度
stringBuffer.append(2);
System.out.println(stringBuffer); //1, 2
System.out.println(stringBuffer.length()); //4
String s = stringBuffer.toString();
System.out.println(s.charAt(0)); //1
②String.split()用法
public String[] split(String regex)
Splits this string around matches of the given regular expression.
This method works as if by invoking the two-argument split method with the given expression and a limit argument of zero. Trailing empty strings are therefore not included in the resulting array.
The string “boo:and:foo”, for example, yields the following results with these expressions:
Split examples showing regex and result
Regex Result
以 : 作为分隔符
{ “boo”, “and”, “foo” }
以 o 作为分隔符
{ “b”, “”, “:and:f” }
此方法的总体功能就是,以给定的分隔符对字符串进行分割,将分割后的一段段的字符串存入字符串数组中并返回。
实例:
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
String s = "aaa,bbb,cc,dd,e";
for (String string : s.split(",")) {
list.add(string); //从而看出String.split()自身就可以当数组使用
}
String[] stringArray = s.split(",");
for (String string2 : stringArray) {
System.out.println(string2); //和上面的for循环是一样的,只不过多了一步存String.split()的结果
}
for (String string3 : list) {
System.out.println(string3);
}
}
两处输出值都是
aaa
bbb
cc
dd
e