今日题目:
今天主要学习了二叉树的序列化和反序列化相关的题型以及做题思路,将一个二叉树进行序列化在很多题目中会用到,通常的做法是将 TreeNode 序列化为一个 String,分隔符和 null 可以使用特殊值,比如 ,
作为分隔符,#
作为 null,这样通过二叉树遍历的思路就可以轻松将一个 tree 序列化为一个 String。
除此之外,第二个题目还学会了后序遍历的独特特点:前序位置的代码只能从函数参数中获取父节点传递来的数据,而后序位置的代码不仅可以获取参数数据,还可以获取到子树通过函数返回值传递回来的数据。所以当我们需要在遍历过程的每一步中使用到子树的内容,那这部分代码需要放在遍历的后序位置上。
LC 297. 二叉树的序列化与反序列化 【classic】 ⭐⭐⭐⭐⭐
这个题目就是让我们实现将一个二叉树转化为 String,再将 String 还原为二叉树。通过这个题目,我们可以学习如何序列化以及反序列化一个二叉树。
这里需要明确一个关键点:之前我们说需要前序+后序两个遍历序列才能还原一个二叉树,但现在我们想只通过前序或后序遍历二叉树得到 String 后,再从这个 String 还原二叉树,这可能嘛?这其实可以的,关键原因在于,我们序列化的 String 中记录了 NULL 节点,而之前的遍历序列中没有 NULL。正是因为序列化结果中记录了 NULL,我们才可以还原这个二叉树。
1)序列化逻辑
直观的,我们可以通过前序遍历来序列化二叉树:
前序遍历的递归遍历逻辑写成 serializeHelper()
函数,利用 StringJoiner 来完成字符串的拼接:
可以看到,这里的 serializeHelper()
函数的逻辑就是前序递归遍历的逻辑架构。
2)反序列化逻辑
序列化部分将二叉树转换为 String,而反序列化则将 String 还原为二叉树,所以我们需要根据我们序列化时的特点来完成反序列化。
整体思路:先对 String 按照分隔符 SEP 将其切分,从而得到由元素组成的 List<Integer>
,空节点就用 null 表示。然后将列表的第一个元素取出来作为 root value,然后再依次反序列化左子树和右子树。
图示如下:
代码如下:
这里也是递归的逻辑,全局使用同一个 nodeVals
列表,并且每次会移除掉首元素来构建 root,所以反序列化完成左子树后,nodeVals
列表就会正好只剩下右子树的所有节点。
以上就是使用前序遍历的思路完成了二叉树的序列化和反序列化。
除了使用前序遍历的思路外,还可以使用后序遍历和层序遍历的思路来完成,详见 东哥带你刷二叉树(序列化篇) | labuladong 文章。
LC 652. 寻找重复的子树 【稍有难度】
这个题目我们需要学会两个点:
- 遍历时后序位置上的独特点:前序位置的代码只能从函数参数中获取父节点传递来的数据,而后序位置的代码不仅可以获取参数数据,还可以获取到子树通过函数返回值传递回来的数据。所以,一旦你发现题目和子树的内容有关,那大概率要给函数设置合理的定义和返回值,在后序位置写代码了。
- 对二叉树序列化的运用
这个题的基本思路很容易想到:遍历二叉树,遍历的每一步都记录下这个子树的结构,并检查是否有重复,有重复的话就将节点加入到 result 中。
这个题目有两个关键点:
- 因为在遍历节点时,我们需要知道整个子树的内容,所以需要在后序位置上记录子树。
- 记录子树结构的方式就是:序列化子树得到 String,并将其记录下来
在这里,我们序列化的方法可以采用与上一个题类似的思路,将序列化的逻辑加入到递归遍历的过程中,并在遍历过程中记录所有子树:
因为我们结果集中不允许有重复,所以与 history 相重复的节点只能有一个加入到 result 中,所以 history 中使用 map 的 value 来记录是否已经加入到 result 中。
这个题目,如果能认识到后序遍历的特点和掌握二叉树的序列化,那这个题目就难度还行。