一天一大 leet(二叉树的序列化与反序列化)难度:困难 DAY-16

20200616

img

题目(难度:困难):

序列化是将一个数据结构或者对象转换为连续的比特位的操作,
进而可以将转换后的数据存储在一个文件或者内存中,
同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

请设计一个算法来实现二叉树的序列化与反序列化。
这里不限定你的序列 / 反序列化算法执行逻辑,
你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

示例

你可以将以下二叉树:
    1
   / \
  2   3
     / \
    4   5

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

说明

不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。

官方答案

官方提供的解法暂无 javascript,根据官方逻辑使用 javascript 实现:

深度优先搜索

二叉树的序列化本质上是对其值进行编码,更重要的是对其结构进行编码。
可以遍历树来完成上述任务。众所周知,我们一般有两个策略:BFS / DFS。

  • BFS 可以按照层次的顺序从上到下遍历所有的节点
  • DFS 可以从一个根开始,一直延伸到某个叶,然后回到根,到达另一个分支。根据根节点、左节点和右节点之间的相对顺序,可以进一步将 DFS 策略区分为:
    • 先序遍历
    • 中序遍历
    • 后序遍历

img

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
  1. TreeNode 序列化(TreeNode -> String)
  • 任选一个字符可以得到其 left 和 right 则
  • 递归找到所有字符拼接成字符串
  1. 字符串 反序列化(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)

公号: 坑人的小书童
坑人的小书童

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
探险家小扣的行动轨迹,都将保存在记录仪中。expeditions[i] 表示小扣第 i 次探险记录,用一个字符串数组表示。其中的每个「营地」由大小写字母组成,通过子串 -> 连接。例:"Leet->code->Campsite",表示到访了 "Leet"、"code"、"Campsite" 三个营地。expeditions[0] 包含了初始小扣已知的所有营地;对于之后的第 i 次探险(即 expeditions[i] 且 i > 0),如果记录中包含了之前均没出现的营地,则表示小扣 新发现 的营地。 请你找出小扣发现新营地最多且索引最小的那次探险,并返回对应的记录索引。如果所有探险记录都没有发现新的营地,返回 -1。注意: 大小写不同的营地视为不同的营地; 营地的名称长度均大于 0。用python实现。给你几个例子:示例 1: 输入:expeditions = ["leet->code","leet->code->Campsite->Leet","leet->code->leet->courier"] 输出:1 解释: 初始已知的所有营地为 "leet" 和 "code" 第 1 次,到访了 "leet"、"code"、"Campsite"、"Leet",新发现营地 2 处:"Campsite"、"Leet" 第 2 次,到访了 "leet"、"code"、"courier",新发现营地 1 处:"courier" 第 1 次探险发现的新营地数量最多,因此返回 1。示例 2: 输入:expeditions = ["Alice->Dex","","Dex"] 输出:-1 解释: 初始已知的所有营地为 "Alice" 和 "Dex" 第 1 次,未到访任何营地; 第 2 次,到访了 "Dex",未新发现营地; 因为两次探险均未发现新的营地,返回 -1
04-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值