JS验证二叉树的前序序列化

138 阅读3分钟

正题

序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #

     _9_
    /   \
   3     2
  / \   / \
 4   1  #  6
/ \ / \   / \
# # # #   # #

例如,上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,6,#,#",其中 # 代表一个空节点。给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。每个以逗号分隔的字符或为一个整数或为一个表示 null 指针的 '#' 。你可以认为输入格式总是有效的,例如它永远不会包含两个连续的逗号,比如 "1,,3" 。

示例 1:

输入: "9,3,4,#,#,1,#,#,2,#,6,#,#"
输出: true

示例 2:

输入: "1,#"
输出: false

示例 3:

输入: "9,#,#,1"
输出: false

解析:已知给定数组是按照二叉树的前序遍历的顺序来给定的,那么首先复习和了解一下二叉树的前序遍历,具体可以参考我之前的文章 二叉树的前序遍历. 二叉树前序遍历的顺序是: 根-左-右。 那么如何验证一个二叉树是否是序列化的呢?首先每个节点下面必有左右节点,如果为空那么以'#'表示,也可视为有节点(#节点),如果缺少节点,那么该数列必然为非序列化二叉树数列。

当我们在做前序遍历的时候首先想到的就是推栈的做法,按照遍历顺序倒退将元素依次推入栈中,然后按照顺序取出,同样的,验证是否序列化也可以按照此方法去做。

接下来就是如何判断他是否为序列化?按照之前的想法:每个节点下面必有左右节点,如果为空那么以'#'表示,也可视为有节点(#节点),如果缺少节点,那么该数列必然为非序列化二叉树数列这是判断的唯一条件。,所以我们应该关注每个节点下面是否还有左右节点,直到 # 为止。

首先看最简单的一种情况:

image.png

由于9包含了左右两个节点那么我们可以认为他是序列化。

再继续看:

image.png

左节点再次延伸,可以看到3也有了左右节点,但依然是序列化。那么我们可不可以把它简化为一开始最简单的情况呢?我们可以认为 3 节点已经是序列化的字节点了,那么后面的计算无需再考虑3,是否可以化繁为简,直接将3看作是#节点,意义就是我们认为3已经可以算是到头了,因为3这个节点已经是序列化的了,再往后计算将会重复计算。所以可以得出下图:

1.gif

由上图可以看出,3,#,# 被看作一个整体,被认为是 #节点即可化繁为简。

那么这样适用于复杂的场景吗?不妨一看:

1.gif

那么最终剩下的就是一个 #

代码:

/**
 * @param {string} preorder
 * @return {boolean}
 */
var isValidSerialization = function(preorder) {
   const list = preorder.split(',')
   const stack = []
   list.forEach(item => {
      stack.push(item)
      while(stack[stack.length - 2] === '#' && stack[stack.length - 1] === '#') {
          if (stack[0] === '#') {
              return false
          }
          stack.pop()
          stack.pop()
          stack.pop()
          stack.push('#')
      }
   })
   console.log(stack)
   return stack.length === 1 && stack[0] === '#'
};

总体思路:

1.按照前序遍历的顺序,将节点推入栈中。

2.栈中如果倒数3个节点为 [....,x,'#','#'],那么就将后三个节点替换为 #

3.最终如果栈中剩下一个 # 那么可以认为是二叉树序列化

特殊处理: 栈不能以 # 开头,因此有: if (stack[0] === '#') { return false }