剑指 Offer 37. 序列化二叉树 / leetcode 297:
二叉树的序列化与反序列化
题目链接:https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/
代码,注释很详细,直接看也行:
// BFS,层序遍历 时间:O(n);空间:因为用到额外的queue ,O(n)
// 先自定义一个序列化规则 [xx, xx, ..., null]
public class Codec {
// encoder 序列化
public String serialize(TreeNode root) {
StringBuilder resString = new StringBuilder("["); //先设置一个字符串 以 [ 开头
//BFS
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
TreeNode curNode = queue.remove();
if(curNode == null){
resString.append("null,"); // null,带上,作为字符串里的一个占位符
}else{
resString.append(curNode.val + ","); // 把节点值加到序列化后的字符串上 带上分隔符,
queue.add(curNode.left);
queue.add(curNode.right);
}
// if(curNode.left != null) queue.add(curNode.left); //用这输出[1,4,5] 警示自己
// if(curNode.right != null) queue.add(curNode.right);
}
resString.setLength(resString.length() - 1); // 去掉最后一个要转换的节点值后面的分隔符 ,
resString.append("]"); // 字符串结尾 ]
return resString.toString();
}
// decoder 反序列化
public TreeNode deserialize(String data) {
//substring截取字符串,就是去掉前后的[ ] 用,分割出各个val
String[] nodesStr = data.substring(1, data.length()-1).split(",");
//这里为了方便,单独写一个由字符串数由字符串转化为节点的方法:strToNode
TreeNode root = strToNode(nodesStr[0]); //获取出root节点 val就是字符串数组的第0号
Queue<TreeNode> parentsQueue = new LinkedList<>(); // 下面当前节点的父节点,装那些没有重建完左右子节点的父节点的队列
TreeNode parentNode = root; //最开始的父亲节点是root
boolean isLeft = true; //假设当前开始是左子节点
//类似于BFS去重建
for(int i = 1; i < nodesStr.length; i++){ //for循环从root节点值之后也就是i = 1开始(因为是层序遍历)遍历转化
TreeNode curNode = strToNode(nodesStr[i]); //把切开的字符串的对应位置的string类型的val转换为当前对应节点node.val
if(isLeft){
parentNode.left = curNode;
}else{
parentNode.right = curNode;
}
if(curNode != null){
parentsQueue.add(curNode); // 把当前节点变成新的父节点 入队
}
isLeft = !isLeft; // 不管之前是重建的左子节点还是右子节点,接下来转成相反的
if(isLeft){ //此时又会成为左子节点
parentNode = parentsQueue.peek(); //拿出新的作为父节点
// parentsQueue.remove(); //删除旧的父节点 用remove时,当队列为空时,会报异常而 poll返回null
parentsQueue.poll(); //
}
}
return root;
}
// 将序列化之后的字符串型val转换并添加到树的节点中,转换
public TreeNode strToNode(String val){
if(val.equals("null")){
return null;
}else{
return new TreeNode(Integer.valueOf(val)); //把str的String类型的数值val转换为原生的Interger型的val
}
}
}
思路:
1、序列化
1、序列化其实就是一个encoder,将给定的二叉树转换类型为字符串String类型,反之亦反。
2、先要定义自己的序列化规则:[xx, xx, …, null]
(1)分隔不同树用[里面的元素是属于同一个树的];
(2)节点之间用逗号 ,
(3)节点的val值,就是把int类型的val转换为String类型的val存起来,用到StringBuilder,这里不用考虑线程安全
(4)特殊的,当树为空时,返回“null,”这里作为一个标识符放在字符串里面;
3、取出当前节点,要把节点的值(加上分隔符逗号)append到字符串上
4、取出节点后,如果有左右子节点,那么入队该节点
5、再通过设置字符串的长度来对输出字符串要去掉末尾的逗号,再append以 ] 作为字符串的结尾
6、最后将字符串返回String型的数字(toString())
2、辅助函数strToNode
定义一个由字符串转化为节点的方法:
直接new一个节点,利用Integer.valueOf(String),将字符串数字转化为int型的val添加到节点中。
3、反序列化
1、首先要把树的所有节点的val从字符串中提出来: (1)先subString(1, str.length()-1)去掉字符串中[ ],就是截取出index=1到]的前一位字符,再用.split(“,”),号为分隔符分隔开节点的值,就是切开序列化后的字符串。
2、再依次去for循环遍历切出来的各个字符串(主要是val),重建节点,这里定义一个将字符串转化为节点的辅助函数,将字符串中的String转化为int类型的val,加入到新节点中。Integer.valueOf(str)
3、字符串被切开后的第一个字符便是根节点(因为是层序遍历的从根节点出发的)
4、重构当前节点,直接把nodesString的值传进去去构建节点
5、这里注意:当在字符串数组中遍历到当前节点时,是不知道其子节点(子节点只会在后面)的,因为层序遍历的顺序决定了到此还未遍历到其子节点,只知道它的父节点(父节点一开始是root)的
① 所以将其父节点存在Queue当中(类似于层序遍历),将还没有更新完子节点的父节点都会放入其中,每次解决完一个父节点,就把该父节点poll出去
② 还需要一个boolean来标识当前节点是其父节点的左孩子节点还是右孩子节点,如果是左孩子,那么当前父节点的左孩子就指向当前节点,反之同理。前提转换出了当前节点
③ 不论是更新完左或者右孩子,直接取反去更新另一个方向的即可。左更完就去更右孩子,此时当前节点的父节点解决了,但是当前节点作为父节点时呢?所以当前节点不为空进队,之后又变回了左孩子就意味着要拿出新的父节点并把当前父节点删除
6、构建完直接return root即可
之前做的时候是参考B站一个大佬小姐姐的讲解~时间久了找不到链接了…