331 验证二叉树的前序序列化-中等
题目:
序列化二叉树的一种方法是使用 前序遍历 。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #。
例如,上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,6,#,#",其中 # 代表一个空节点。
给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。
保证 每个以逗号分隔的字符或为一个整数或为一个表示 null 指针的 '#' 。
你可以认为输入格式总是有效的
例如它永远不会包含两个连续的逗号,比如 "1,,3" 。
注意:不允许重建树。
分析:
算法1:
还是用递归的思想,只要满足 数字,#,#
这样的模式,那么就是合法的二叉树。那么是从底部向上分析还是从顶向下分析呢?很显然需要从底向上,因为只要叶子节点满足 数字,#,#
的模式,进而将其替换为 #
,继续判断其父结点。
所以这里需要用到栈结构,从左到右依次入栈,当栈顶的三个元素满足 数字,#,#
模式时,继续判断。最后栈中应该只剩下一个 #
,那么就是一个合法的二叉树。
// date 2022/10/24
func isValidSerialization(preorder string) bool {
stack := make([]string, 0, 16)
parts := strings.Split(preorder, ",")
for _, v := range parts {
stack = append(stack, v)
for len(stack) >= 3 && stack[len(stack)-1] == "#" && stack[len(stack)-2] == "#" && stack[len(stack)-3] != "#" {
stack = stack[:len(stack)-3]
stack = append(stack, "#")
}
}
return len(stack) == 1 && stack[0] == "#"
}
算法2:【推荐该算法】
从出度和入度的角度重新理解树。
我们知道对于一个二叉树,所有节点的入度之和等于其出度之和。那么我们就可以根据这个条件进行有效判断。
对于二叉树,我们将空节点也是为叶子节点(即题目中的 #
),那么则有:
所有的非空节点,提供2个出度,1个入度(根节点除外)
所有的空节点,提供0个出度,1个入度
我们在遍历的时候,可以计算出度与入度之差diff = outdegree - indegree
。我们从根开始遍历,当一个节点出现的时候 diff-1
,因为它提供一个入度;当节点不是空节点的时候 diff+2
,因为非空节点提供2个出度。
如果序列式是合法的,那么整个遍历过程中 diff >= 0
,并且最后的结果为零。
这里解释为为什么 diff
初始化为1,因为根节点没有父结点,所以根节点提供2个出度,0个入度。在整个遍历过程中都是先减一,如果是非空节点再加二。所以初始化为1,保证了根节点先减去1个入度,再加上2个出度,正好为2,类似链表中 伪头节点
的做法。
// date 2022/10/24
func isValidSerialization(preorder string) bool {
// diff = outdegree - indegree
diff := 1
parts := strings.Split(preorder, ",")
for _, v := range parts {
diff -= 1
if diff < 0 {
return false
}
if v != "#" {
diff += 2
}
}
return diff == 0
}
最后更新于