一天一大 leet(二叉树的序列化与反序列化)难度:困难 DAY-16
程序员文章站
2022-05-07 23:02:20
...
20200616
题目(难度:困难):
序列化是将一个数据结构或者对象转换为连续的比特位的操作,
进而可以将转换后的数据存储在一个文件或者内存中,
同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。
这里不限定你的序列 / 反序列化算法执行逻辑,
你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
示例
你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 "[1,2,3,null,null,4,5]"
说明
不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。
官方答案
官方提供的解法暂无 javascript,根据官方逻辑使用 javascript 实现:
深度优先搜索
二叉树的序列化本质上是对其值进行编码,更重要的是对其结构进行编码。
可以遍历树来完成上述任务。众所周知,我们一般有两个策略:BFS / DFS。
- BFS 可以按照层次的顺序从上到下遍历所有的节点
- DFS 可以从一个根开始,一直延伸到某个叶,然后回到根,到达另一个分支。根据根节点、左节点和右节点之间的相对顺序,可以进一步将 DFS 策略区分为:
- 先序遍历
- 中序遍历
- 后序遍历
TreeNode:val 当前指针指的值,left 上一个值,right 下一个值
- val -> 1 left -> 2 right -> 3
- val -> 2 left -> null right -> null
- val -> 3 left -> 4 right -> 5
- val -> 4 left -> null right -> null
- val -> 5 left -> null ight -> null
- TreeNode 序列化(TreeNode -> String)
- 任选一个字符可以得到其 left 和 right 则
- 递归找到所有字符拼接成字符串
- 字符串 反序列化(String -> TreeNode)
- 先讲字符串转换成数组
- 遍历数组分别对没有下赋值 left 及 right
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* Encodes a tree to a single string.
*
* @param {TreeNode} root
* @return {string}
*/
var serialize = function (root) {
if (root === null || root.val === null) return 'null,'
return root.val + ',' + serialize(root.left) + serialize(root.right)
}
/**
* Decodes your encoded data to tree.
*
* @param {string} data
* @return {TreeNode}
*/
var deserialize = function (data) {
function tree(list) {
if (list.length === 0);
let val = list.shift()
if (val === 'null') return null
let node = new TreeNode(val)
node.left = tree(list)
node.right = tree(list)
return node
}
let list = data.split(',')
return tree(list)
}
/**
* Your functions will be called as such:
* deserialize(serialize(root));
*/
其他解法
BFS
序列化 —— 常规的 BFS
- null 节点也入列,说它是真实节点也行,它有对应的"X",只是没有子节点入列
- 考察出列节点
- 如果不为 null,则将它的值推入 res 数组,并将它的左右子节点入列
- 如果是 null 节点,则将 ‘X’ 推入 res 数组
- 出列、入列,直到队列为空,所有节点遍历完,res 数组也构建完,转成字符
反序列化——也是 BFS,父节点出列,子节点入列
- 除了第一个 ROOT 值,其他节点值都成对出现,分别对应左右子节点
- 我们从第二项开始遍历,每次考察两个节点值
- 因为先生成的节点,会成为之后生成的节点的父亲,用一个 queue 暂存一下
- queue 初始放入根节点。父节点出列即考察它,新创建的子节点入列
同时考察父节点,和两个子节点值
- 出列的父节点,它对应到指针指向的左子节点值,和指针右边的右子节点值
- 如果子节点值不为 ‘X’ ,则为它创建节点,成为父节点的子节点,并作为未来的父节点入列
- 如果子节点值为 ‘X’,什么都不做
- 所有父节点(真实节点)都会在 queue 里走一次
/**
* Encodes a tree to a single string.
*
* @param {TreeNode} root
* @return {string}
*/
var serialize = function (root) {
const queue = [root]
let res = []
while (queue.length) {
const node = queue.shift()
if (node) {
// 出列的节点 带出子节点入列
res.push(node.val)
queue.push(node.left) // 不管是不是null节点都入列
queue.push(node.right)
} else {
res.push('X')
}
}
return res.join(',')
}
/**
* Decodes your encoded data to tree.
*
* @param {string} data
* @return {TreeNode}
*/
var deserialize = function (data) {
if (data == 'X') return null // 就一个'X',只有一个null
const list = data.split(',') // 序列化字符串转成list数组
const root = new TreeNode(list[0]) //首项是根节点值,为它创建节点
const queue = [root] // 初始放入root,待会出列考察
let cursor = 1 // 从list第二项开始遍历
while (cursor < list.length) {
// 指针越界
const node = queue.shift() // 父节点出列考察
const leftVal = list[cursor] // 获取左子节点值
const rightVal = list[cursor + 1] // 获取右子节点值
if (leftVal !== 'X') {
// 左子节点值是有效值
const leftNode = new TreeNode(leftVal) // 创建节点
node.left = leftNode // 成为当前出列节点的左子节点
queue.push(leftNode) // 它是未来的爸爸,入列等待考察
}
if (rightVal !== 'X') {
// 右子节点值是有效值
const rightNode = new TreeNode(rightVal) // 创建节点
node.right = rightNode // 成为当前出列节点的右子节点
queue.push(rightNode) // 它是未来的爸爸,入列等待考察
}
cursor += 2 // 指针前进2位
}
return root // 返回根节点
}
菜鸟的自白
这道题本地调试有点曲折
题目提示了:
// Definition for a binary tree node.
function TreeNode(val) {
this.val = val
this.left = this.right = null
}
// Your functions will be called as such:
deserialize(serialize(root))
但是在本地调试的时候还是有点地方要修改的:
- 引入 TreeNode 函数
- 反序列化需要传入序列化之后的数据,那本地先声明一个 reeNode 形式的序列化数据就太麻烦了
- serialize(deserialize(‘1,2,3,null,null,4,5’))
- 反向调用先传入 String -> Object -> String 来验证逻辑
博客: 小书童博客(http://gaowenju.com)
公号: 坑人的小书童