写点什么

算法攻关 - 序列化和反序列化二叉树 O(n)_offer37

发布于: 2021 年 03 月 14 日
算法攻关-序列化和反序列化二叉树O(n)_offer37

一、题目描述

请实现两个函数,分别用来序列化和反序列化二叉树。


示例: 


你可以将以下二叉树:


   1

  / \

 2   3

    / \

   4   5


序列化为 "[1,2,3,null,null,4,5]"


来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二、思考

从我们这里看到的题目要求是将一颗二叉树能够序列化,并还能够将其反序列化成二叉树。也就是说我们需要保持 null 值来标识某个节点是否还有子节点(第一点要求),第二点要求:序列化的时候我们使用 BFS,三种遍历方式,前、中、后的选择哪个会更好,前序遍历,因为我们一开始就知道了 root 节点,会方便加速我们的序列化和反序列化,万事开头难嘛。

序列化->

反序列化->可举例如下图:


三、代码实现

利用 BFS 实现的

class TreeNode{        int val;        TreeNode left;        TreeNode right;        TreeNode(int x){            this.val = x;        }    }    // Encodes a tree to a single string.    public String serialize(TreeNode root) {        //第一步:边界特殊值处理        if (root == null){            return "";        }        //第二步:BFS搜索,遍历放到队列中        Queue<TreeNode> queue = new LinkedList<>();        queue.offer(root);        //第三步:将BFS结果进行封装,并处理好边界的符号        StringBuilder sb = new StringBuilder();        sb.append("[");        //第四步:中序遍历,进行节点值的处理        while(!queue.isEmpty()){            TreeNode node = queue.poll();            if (node != null){                sb.append(node.val).append(",");                queue.offer(node.left);                queue.offer(node.right);            }else{                sb.append("null,");            }        }        sb.deleteCharAt(sb.length() - 1);        sb.append("]");        return sb.toString();    }
// Decodes your encoded data to tree. public TreeNode deserialize(String data) { //第一步如果字符串为空,则返回null if (data == ""){ return null; } //第二步:将字符串构建成数组 String varStr= data.substring(1,data.length()-1); String [] arrStr= varStr.split(","); //第三步:构建root节点,因为我们是前序遍历 TreeNode root = new TreeNode(Integer.valueOf(arrStr[0])); //第四步:队列遍历来BFS获取每个节点应该存放的节点值 Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); int i = 1; //第五步:将数组的值进行构造节点并为下一轮队列遍历做准备 while(!queue.isEmpty() && i <arrStr.length){ TreeNode node = queue.poll(); if (!arrStr[i].equals("null")){ node.left = new TreeNode(Integer.valueOf(arrStr[i])); queue.offer(node.left); } i++; if (!arrStr[i].equals("null")){ node.right = new TreeNode(Integer.valueOf(arrStr[i])); queue.offer(node.right); } i++; } return root; }
复制代码

复杂度分析:

时间复杂度 O(N): N 为二叉树的节点数,层序遍历需要访问所有节点,最差情况下需要访问 N+1 个 null ,总体复杂度为 O(2N + 1) = O(N) 。

空间复杂度 O(N): 最差情况下,队列 queue 同时存储 (N + 1)/2 节点(或 N+1 个 null ),使用 O(N) ;列表 res 使用 O(N) 。

复杂度分析:

时间复杂度 O(N): N 为二叉树的节点数,按层构建二叉树需要遍历整个节点,其长度最大为 2N+1 。

空间复杂度 O(N): 最差情况下,队列 queue 同时存储 (N + 1)/2}个节点,因此使用 O(N)O(N) 额外空间。


如果这个时候还记得我们遍历二叉树的话,我们应该忘记不了 DFS。那么 DFS 我们用递归是否能够简化下代码呢?

序列化

递归的第一步都是特例的处理,因为这是递归的中止条件:如果根节点为空,返回”null“

序列化的结果为:根节点值 + "," + 左子节点值(进入递归) + "," + 右子节点值(进入递归)

递归就是不断将“根节点”值加到结果中的过程

反序列化

先将字符串转换成队列

接下来就进入了递归

i. 弹出左侧元素,即队列出队

ii. 如果元素为“null”,返回 null

iii. 否则,新建一个值为弹出元素的新节点

iv. 其左子节点为队列的下一个元素,进入递归;右子节点为队列的下下个元素,也进入递归

v. 递归就是不断将子树的根节点连接到父节点的过程

参考链接:https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/solution/jian-zhi-offer-37-xu-lie-hua-er-cha-shu-zgixr/



// Encodes a tree to a single string. public String serialize(TreeNode root) { if(root == null){ return "null"; } StringBuilder sb = new StringBuilder();
//中序遍历-利用递归思路将根-左子树-右子树进行处理 return sb.append(root.val).append(",").append(serialize(root.left)).append(",").append(serialize(root.right)).toString(); }
// Decodes your encoded data to tree. public TreeNode deserialize2(String data) { //构建队列数据 Queue<String> queue = new LinkedList<>(Arrays.asList(data.split(","))); return dfs(queue); }
private TreeNode dfs(Queue<String> queue) { //遍历队列数据进行中序遍历 String val = queue.poll(); if("null".equals(val)){ return null; } TreeNode root = new TreeNode(Integer.parseInt(val)); root.left = dfs(queue); root.right = dfs(queue); return root; }
复制代码

复杂度分析


时间复杂度:O(n)


  • 空间复杂度:O(n)

四、小结

其实虽然本题是 hard 题目,但是实际上本身来说,还是考查了面试者对于 DFS 和 BFS 的灵活应用。


发布于: 2021 年 03 月 14 日阅读数: 6
用户头像

小胜靠智,大胜靠德 2019.06.27 加入

历经创业、京东、腾讯、滴滴公司,希望能够有些东西一起分享。公众号:小诚信驿站,微信/CSDN搜索:wolf_love666

评论

发布
暂无评论
算法攻关-序列化和反序列化二叉树O(n)_offer37