基本思路
无论使用何种「遍历方式」进行二叉树存储,为了方便,我们都需要对空节点有所表示。
其实题目本身的样例就给我们提供了很好的思路:使用层序遍历的方式进行存储,对于某个叶子节点的空节点进行存储,同时确保不递归存储空节点对应的子节点。
层序遍历
我们使用 0x3f3f3f3f
作为无效值(当然也可以不使用数字,使用某个特殊字符进行表示,只要能在反序列时有所区分即可),并建立占位节点 emptyNode
用来代指空节点(emptyNode.val = INF
)。
序列化:先特判掉空树的情况,之后就是常规的层序遍历逻辑:
- 起始时,将
root
节点入队; - 从队列中取出节点,检查节点是否有左/右节点:
- 如果有的话,将值追加序列化字符中(注意使用分隔符),并将节点入队;
- 如果没有,检查当前节点是否为
emptyNode
节点,如果不是emptyNode
说明是常规的叶子节点,需要将其对应的空节点进行存储,即将emptyNode
入队;
- 循环流程 ,直到整个队列为空。
反序列:同理,怎么「序列化」就怎么进行「反序列」即可:
- 起始时,构造出
root
并入队; - 每次从队列取出元素时,同时从序列化字符中截取两个值(对应左右节点),检查是否为
INF
,若不为INF
则构建对应节点; - 循环流程 ,直到整个序列化字符串被处理完(注意跳过最后一位分隔符)。
代码:
public class Codec { int INF = 0x3f3f3f3f; TreeNode emptyNode = new TreeNode(INF); public String serialize(TreeNode root) { if (root == null) return ""; StringBuilder sb = new StringBuilder(); // 使用队列进行层序遍历,起始先将 root 放入队列 Deque<TreeNode> d = new ArrayDeque<>(); d.addLast(root); while (!d.isEmpty()) { // 每次从队列中取出元素进行「拼接」,包括「正常节点」和「叶子节点对应的首位空节点」 TreeNode poll = d.pollFirst(); sb.append(poll.val + "_"); // 如果取出的节点不为「占位节点」,则继续往下拓展,同时防止「占位节点」不继续往下拓展 if (!poll.equals(emptyNode)) { d.addLast(poll.left != null ? poll.left : emptyNode); d.addLast(poll.right != null ? poll.right : emptyNode); } } return sb.toString(); } public TreeNode deserialize(String data) { if (data.equals("")) return null; // 根据分隔符进行分割 String[] ss = data.split("_"); int n = ss.length; // 怎么序列化就怎么反序列化 // 使用队列进行层序遍历,起始先将 root 构建出来,并放入队列 TreeNode root = new TreeNode(Integer.parseInt(ss[0])); Deque<TreeNode> d = new ArrayDeque<>(); d.addLast(root); for (int i = 1; i < n - 1; i += 2) { TreeNode poll = d.pollFirst(); // 每次从中取出左右节点对应 val int a = Integer.parseInt(ss[i]), b = Integer.parseInt(ss[i + 1]); // 如果左节点对应的值不是 INF,则构建「真实节点」 if (a != INF) { poll.left = new TreeNode(a); d.addLast(poll.left); } // 如果右节点对应的值不是 INF,则构建「真实节点」 if (b != INF) { poll.right = new TreeNode(b); d.addLast(poll.right); } } return root; } }
- 时间复杂度:
- 空间复杂度:
最后
这是我们「必考真题 の 精选」系列文章的第 No.93
篇,系列开始于 2021/07/01。
该系列会将牛客网中「题霸 - 面试必考真题」中比较经典而又不过时的题目都讲一遍。
在提供追求「证明」&「思路」的同时,提供最为简洁的代码。
欢迎关注,交个朋友 (`・ω・´)