[树 字符串] 297. 二叉树的序列化与反序列化(DFS、BFS)

10 篇文章 0 订阅
7 篇文章 0 订阅

297. 二叉树的序列化与反序列化

题目链接:https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/


分类

  • 树、字符串(二叉树序列化:树 → 字符串,二叉树反序列化:字符串 → 树);
  • DFS:(思路2)
    • 序列化:先序遍历得到序列 + 空节点也加入序列;
    • 反序列化:全局遍历序列(全局变量 或 队列) + 前序遍历构建二叉树 ;
  • BFS:(思路1)
    • 序列化:层次遍历得到序列 + 空节点也加入序列;
    • 反序列化:全局遍历序列 + 层次遍历构建二叉树;
  • 集合知识:数组转存到队列(数组 -(Arrays.asList)-> 列表 -(构造器)->队列)

在这里插入图片描述

思路1:BFS

序列化:对二叉树做BFS即做层次遍历,遇到非空节点就向序列插入节点值,遇到空节点时就向序列插入"null",节点之间也要用","隔开,整个序列的首尾分别是 “[” 和 “]” 。

算法设计:

  1. 设置一个队列对二叉树做层次遍历,需要注意的是如果节点为空节点也要入队。再开辟一个StringBuilder sb来存放序列化过程中的序列,初始时先向sb尾部插入 “[”;

  2. 层次遍历按常规方法实现,每出队一个元素,判断它是不是null:

    如果不是null节点,就将其左右孩子入队(就算左右孩子为空节点也入队),再将节点的val加入sb末尾,再加上逗号;

    如果是null节点,就不再处理它的左右孩子,只将"null"插入sb末尾,再加上逗号。

    注意:为了简化代码,每个元素输出到字符串上时统一在后面加上逗号,在得到最终的字符串时再将最末尾的逗号去掉,换成 “]”,就能得到符合题目要求的序列。

  3. 并不是所有空节点都要输出"null",例如示例中4和5的孩子节点都为null,但不需要再输出这些null了,体现在层次遍历过程中就是:如果此时队内所有元素都是空节点,就不再输出到sb上。

    这样的设置在一些边界情况如:root==null,会输出"[]",所以我们需要一些细节上的调整,见代码注释。

由此带来一个问题:如何判断当前队列内是不是全为空节点?

方法:设置一个计数器count,在遍历过程中记录队列内的空节点个数,每入队一个空节点count+1,每出队一个空节点count-1。如果count == queue.size(),说明队内全是空节点。

  • 注意:queue.size()返回队内元素个数,元素null也计算在内。

反序列化:使用BFS将序列化生成的字符串再转换为二叉树。

算法设计

  1. 将序列化得到的字符串去除首尾的"[“和”]",再按逗号分割,得到字符串数组nodes;设置一个队列用于对已生成的二叉树做BFS;

  2. 先取字符串数组nodes[0]生成根节点,然后将根节点入队,设置一个指针idx作为nodes数组上的工作指针,初始时idx=1;

  3. BFS的过程其实是边创建节点边做BFS,每一轮就拿此时nodes数组上的nodes[idx]创建节点,然后将其作为当前节点的孩子节点。先确定当前层的节点个数(=此时的队列长度),然后将当前层的节点依次从队列中弹出,每弹出一个节点top,此时nodes数组上nodes[idx]和nodes[idx+1]分别是它的左右孩子节点:

    以左孩子为例:

    – 如果nodes[idx]不为"null",就将字符串元素转换成数字创建对应的TreeNode,作为top的左孩子,然后将top.left加入队列;

    – 如果nodes[idx]为"null",就直接跳过;

    左孩子处理完后idx+1;

    右孩子同理,在处理完毕后,同样需要再次将idx+1;

  • 注意:无论当前nodes[idx]是否为"null",每遍历完一个元素,idx+1。(易遗漏)

直到队列为空,BFS结束。

实现代码:

public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        //特殊用例
        //if(root == null) return "[null]";

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        //sb存放生成的序列
        StringBuilder sb = new StringBuilder();
        //先向sb加入"["
        sb.append("[");
        int count = 0;//记录队内空节点个数(不包括根节点)
        while(!queue.isEmpty()){
            int size = queue.size();
            //如果队内元素都是空节点,则直接退出(根节点为空的情况不会退出)
            if(size == count) break;
            for(int i = 0; i < size; i++){
                TreeNode top = queue.poll();
                //如果出队的节点不为空,则取节点值加入sb末尾(无论是不是最后一个节点后面都加上逗号,在收尾时再删除)
                if(top != null){
                    sb.append(top.val).append(",");
                    //左右孩子入队
                    queue.offer(top.left);
                    queue.offer(top.right);
                    //如果入队的是空节点,则count增加
                    if(top.left == null) count++;
                    if(top.right == null) count++;
                }
                //如果出队的节点为空,则将"null"加入sb末尾,不需要再处理它的左右孩子
                else{
                    sb.append("null").append(",");
                    count--;
                }
            }
        }
        //将sb末尾的逗号删除
        sb.setLength(sb.length() - 1);
        //向sb末尾加入"]"后再返回
        return sb.append("]").toString();
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        //特殊用例
        if(data.equals("[null]")) return null;
        //为了便于处理将data首尾中括号去除,然后按逗号分割成字符串数组
        String[] nodes = data.substring(1, data.length() - 1).split(",");
        
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode root = new TreeNode(Integer.parseInt(nodes[0]));
        
        queue.offer(root);

        int idx = 1;//nodes上的工作指针
        while(!queue.isEmpty()){
            int size = queue.size();//二叉树当前层的节点个数
            for(int i = 0; i < size; i++){
                TreeNode top = queue.poll();
                //添加左孩子:字符串元素不为null才添加为左孩子,否则直接跳过
                if(idx < nodes.length && !nodes[idx].equals("null")){
                    top.left = new TreeNode(Integer.parseInt(nodes[idx]));
                    queue.offer(top.left);
                   
                }
                idx++;//无论字符串是否为"null",工作指针都要+1
                //添加右孩子:字符串元素不为null才添加为右孩子,否则直接跳过
                if(idx < nodes.length && !nodes[idx].equals("null")){
                    top.right = new TreeNode(Integer.parseInt(nodes[idx]));
                    queue.offer(top.right);
                                    
                }
                idx++;
            }
        }
        return root;
    }
}

思路2:DFS

看题解发现首尾两个中括号可以不用加。。。所以思路2的序列不考虑中括号,递归代码更好写。

序列化:
这里使用的是前序遍历,和常规的前序遍历基本相同,不同点在于这里需要将空节点也作为"null"加入遍历序列,元素之间要加逗号隔开。

例如:
    1
   / \
  2   3
     / \
    4   5
得到的前序遍历序列是:1,2,null,null,3,4,null,null,5,null,null    

反序列化:
根据上面序列化得到的前序遍历序列data生成对应的二叉树。我们按次序遍历序列,对每个遍历到的元素按前序遍历的方式构建节点和它的左右孩子。

例如:1,2,null,null,3,4,null,null,5,null,null

访问nodes[0]=1,创建一个节点root,root.val=1;

  • 继续调用递归访问nodes[1]=2,返回val=2的节点作为root的左孩子;
    • 左子树的构建在更深层的递归中自动完成,在返回节点给root时左子树已经构建完成,右子树同理。
  • 继续调用递归访问nodes[2]=null,返回空节点作为root的右孩子。

注意:对nodes的遍历是全局性的,整个递归过程共享它的遍历状态。

所以问题就在于:如何全局遍历nodes数组?

  • 方法1:设置全局变量idx作为遍历nodes的工作指针;
  • 方法2:将nodes转存为队列,每次弹出一个节点。

算法流程:

(方法1)
1、将序列data按逗号分割,得到字符串数组nodes,设置一个全局变量idx作为nodes上的工作指针,初始值=1;

2、进入DFS:

  • 如果nodes[idx]==“null”,返回null;
  • 如果nodes[idx]!=“null”,则将nodes[idx]转成数字,作为val创建一个节点root;
    • idx++,继续调用递归,返回值作为root的左孩子;
    • idx++,继续调用递归,返回值作为root的右孩子。
    • 返回root。

(方法2)
整体流程和方法1大致相同,但需要先将nodes数组转换成队列,在前序遍历构造二叉树的过程中,获取nodes元素时是从队列中弹出队首,不需要idx指针。

知识点:数组转成队列(数组 -(Arrays.asList)-> 列表 -(构造器)->队列)

数组先利用Arrays.asList转换成列表,再利用队列的构造器转存为队列:

Queue<String> queue = new LinkedList<>(Arrays.asList(nodes));
  • 注意:Arrays.asList转换得到的列表长度是不可变的,也没有add,remove方法,同时建议只对引用数组使用,对基本数据类型的数组使用会出错。

实现代码:

//思路2:DFS,先序遍历
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if(root == null) return "null";

        String left = serialize(root.left);
        String right = serialize(root.right);
        return root.val + "," + left + "," + right;//序列不考虑首尾的中括号
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        //特殊用例
        if(data.equals("[null]")) return null;
        //为了便于处理将data按逗号分割成字符串数组
        String[] nodes = data.split(",");
        return helper1(nodes);        
    }
    int idx = 0;//用于全局遍历nodes数组的工作指针
    //反序列化:前序遍历 + 方法1
    private TreeNode helper1(String[] nodes){
        if(nodes[idx].equals("null")) return null;
        TreeNode root = new TreeNode(Integer.parseInt(nodes[idx]));
        idx++;
        root.left = helper1(nodes);
        idx++;
        root.right = helper1(nodes);
        return root;
    }
	//反序列化:前序遍历 + 方法2
    public TreeNode helper2(Queue<String> queue){
        String node = queue.poll();
        if(node.equals("null")) return null;
        TreeNode root = new TreeNode(Integer.parseInt(node));
        root.left = helper2(queue);
        root.right = helper2(queue);
        return root;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值