0x00 题目
序列化二叉树的一种方法是使用 前序遍历
当遇到一个 非空
节点时,可以记录下这个节点的值
如果它是一个空
节点,可以使用一个标记值记录,例如 #
给定一串以 逗号
分隔的序列
验证它是否是 正确
的二叉树的 前序
序列化
编写一个在 不
重构树的条件下的可行算法
每个以逗号分隔的字符,是一个整数
或者是一个表示 null
指针的 #
你可以认为输入格式总是有效的
例如它永远不会包含两个连续的逗号,比如 1,,3
0x01 思路
方法一:栈
先定义一个概念,叫做 槽位
一个 槽位
可以被看作当前二叉树中正在等待 被节点填充
的那些位置
二叉树的建立也伴随着 槽位
数量的变化。每当遇到一个节点时:
a.如果遇到了 空
节点,则要 消耗
一个槽位
b.如果遇到了 非空
节点,则除了 消耗
一个槽位外,还要再 补充
两个槽位
使用栈来维护槽位的变化
栈中的每个 元素
,代表了对应节点处剩余槽位的 数量
而栈顶元素就对应着下一步可用的槽位数量
当遇到 空
节点时,仅将栈顶元素减 1
当遇到 非空
节点时,将栈顶元素减 1
后,再向栈中压入一个 2
无论何时,如果栈顶元素变为 0
,就立刻将栈顶 弹出
遍历结束后,若栈 为空
说明没有待填充的槽位,因此是一个 合法
序列
否则若栈 不为空
,则序列不合法
此外,在遍历的过程中,若槽位数量 不足
,则序列不合法
方法二:计数
能否将方法一的空间复杂度优化至 O(1)
呢?
回顾方法一的逻辑,如果把栈中元素看成一个 整体
即所有剩余槽位的 数量
,也能维护槽位的 变化
因此,只维护一个 计数器
,代表栈中所有元素之 和
其余的操作逻辑均可以保持不变
0x02 解法
语言:Swift
解法一:
func isValidSerialization(_ preorder: String) -> Bool {
let n = preorder.count
var i = 0
var stack: [Int] = [1]
while i < n {
// 槽位数量不足
if stack.isEmpty {
return false
}
// 取一个字符
let sIndex = preorder.startIndex
var index = preorder.index(sIndex, offsetBy: i)
let c = preorder[index]
// 判断字符
if c == "," {
i += 1
}else if c == "#" {
// 空节点
let last = stack.last! - 1
if last == 0 {
stack.removeLast()
}else{
stack[stack.count-1] = last
}
i += 1
}else{
// 读一个数字
var s = Character("c")
while i < n && s != "," {
index = preorder.index(sIndex, offsetBy: i)
s = preorder[index]
i += 1
}
let last = stack.last! - 1
if last == 0 {
stack.removeLast()
}else{
stack[stack.count-1] = last
}
stack.append(2)
}
}
return stack.isEmpty
}
解法二:
func isValidSerializationV2(_ preorder: String) -> Bool {
let n = preorder.count
var i = 0
var slots = 1
while i < n {
if slots == 0 {
return false
}
let sIndex = preorder.startIndex
var index = preorder.index(sIndex, offsetBy: i)
let c = preorder[index]
if c == "," {
i += 1
}else if c == "#" {
slots -= 1
i += 1
}else{
// 读一个数字
var s = Character("c")
while i < n && s != "," {
index = preorder.index(sIndex, offsetBy: i)
s = preorder[index]
i += 1
}
slots += 1 // slots = slots - 1 + 2
}
}
return slots == 0
}