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
}
小笔记应用
|