摘要
在做算法题的时候,树相关的题总是“神神叨叨”的,但其实抓住核心规则,它们也挺有逻辑的。今天这题——LeetCode 255,考的就是:给你一段前序遍历序列,问你它能不能构成一个合法的二叉搜索树(BST)?
这题我们会用 Swift 写一个“靠谱”的解法,边讲边拆解,最后还有可运行的 Demo 方便你直接跑。
描述
原题是这样说的:
给你一个整数数组
preorder
,请判断它是否是某个二叉搜索树的前序遍历结果。
前序遍历的定义我们都知道:根 -> 左 -> 右。
而二叉搜索树(BST)的规则也耳熟能详:左子树所有节点小于根节点,右子树所有节点大于根节点。
所以这题换句话说就是:你要判断一个数组是否满足“BST 的前序遍历规则”。
题解答案
我们可以用一个“单调栈”来模拟这个过程:
遍历这个
preorder
数组,用一个栈保存“还没处理完的子树的根节点”,
每次碰到一个比栈顶小的元素,就是还在当前根节点的左子树;
一旦碰到比栈顶大的元素,就说明该进入右子树了,要不断 pop 栈直到找到合适的“祖先”节点。
顺带维护一个变量 low
,记录当前子树的下界,如果某个元素比这个 low
还小,那就说明不合法。
题解代码分析
func verifyPreorder(_ preorder: [Int]) -> Bool {
var stack = [Int]()
var low = Int.min
for value in preorder {
if value < low {
return false
}
while let last = stack.last, value > last {
low = stack.removeLast()
}
stack.append(value)
}
return true
}
核心点解释:
low
表示当前节点必须大于的下界(来自之前 pop 出的根节点);stack
模拟一个路径栈,遇到“回到右子树”的场景时就一直 pop;如果出现了比
low
还小的值,就说明“回到了左边”,是非法的 BST 遍历。
示例测试及结果
print(verifyPreorder([5, 2, 1, 3, 6])) // true
print(verifyPreorder([5, 2, 6, 1, 3])) // false
解释:
第一组
[5,2,1,3,6]
是合法的 BST 前序遍历;第二组中
6
后出现了1
,说明它跑到了右子树再回到左边,是非法的。
时间复杂度
遍历每个节点一次,
O(n)
每个节点最多 push 和 pop 一次,
O(n)
所以总体复杂度:O(n)
空间复杂度
- 最坏情况下栈里可能有
n
个节点(递增序列),所以:O(n)
总结
这道题说简单也不简单,说复杂也不复杂。它没有让你去构建真正的树结构,而是让你模拟前序遍历的判断过程。只要理解了“左子树 < 根 < 右子树”的 BST 特性,用一个栈就能轻松解决。
这类题在实际应用场景里也不是纸上谈兵,比如:
代码解析器:校验表达式语法是否合法;
编译器优化:判断语法树结构;
数据库索引构建:模拟 B+ 树或 AVL 树结构。
未来展望
如果你能搞懂这题,后续可以挑战一些更硬核的题,比如:
构建 BST 并打印所有路径;
给定后序遍历判断合法性;
还可以去了解一下 AVL 树、红黑树的构造和旋转逻辑。