题目描述
思路解析动画文字版
记住这一句:真节点配齐两个 # 就坍缩成一个 #。下面每一帧都在套它。
栈式消除 · 读入与坍缩:先把序列按逗号切成 13 个记号,挨个读。我们不重建树,只维护一个栈:每读一个记号压栈,栈顶一旦凑出「真值, #, #」就把这三个压成一个 #。读完后栈里恰好只剩一个 #,就合法。
栈式消除 · 读入与坍缩:读入第 0 个 “9”,它是真实节点,直接压入栈顶。栈顶还没凑出「真值, #, #」的形状,不坍缩,继续往后读。
栈式消除 · 读入与坍缩:读入第 1 个 “3”,真实节点压入栈顶。栈顶暂时还不构成可坍缩的形状,接着读下一个。
栈式消除 · 读入与坍缩:读入第 2 个 “4”,真实节点压入栈顶。栈顶暂时还不构成可坍缩的形状,接着读下一个。
栈式消除 · 读入与坍缩:读入第 3 个 “#”(空节点)压栈。栈顶只有一个 #,还差一个,先不坍缩,继续读。
栈式消除 · 读入与坍缩:读入第 4 个 “#”(空节点)压栈,这下栈顶正好是「真值, #, #」,可以坍缩了。
栈式消除 · 读入与坍缩:“4” 的左右孩子都到齐了,以它为根的小子树整体退化成一个空位,把栈顶三个 4, #, # 压成一个 #。坍缩后栈顶不再是可坍缩形状,暂停,接着读下一个记号。
栈式消除 · 读入与坍缩:读入第 5 个 “1”,真实节点压入栈顶。栈顶暂时还不构成可坍缩的形状,接着读下一个。
栈式消除 · 读入与坍缩:读入第 6 个 “#”(空节点)压栈。栈顶只有一个 #,还差一个,先不坍缩,继续读。
栈式消除 · 读入与坍缩:读入第 7 个 “#”(空节点)压栈,这下栈顶正好是「真值, #, #」,可以坍缩了。
栈式消除 · 读入与坍缩:“1” 的左右孩子都到齐了,以它为根的小子树整体退化成一个空位,把栈顶三个 1, #, # 压成一个 #。坍缩后栈顶又凑出「真值, #, #」,继续往下坍。
栈式消除 · 读入与坍缩:“3” 的左右孩子都到齐了,以它为根的小子树整体退化成一个空位,把栈顶三个 3, #, # 压成一个 #。坍缩后栈顶不再是可坍缩形状,暂停,接着读下一个记号。
栈式消除 · 读入与坍缩:读入第 8 个 “2”,真实节点压入栈顶。栈顶暂时还不构成可坍缩的形状,接着读下一个。
栈式消除 · 读入与坍缩:读入第 9 个 “#”(空节点)压栈。栈顶只有一个 #,还差一个,先不坍缩,继续读。
栈式消除 · 读入与坍缩:读入第 10 个 “6”,真实节点压入栈顶。栈顶暂时还不构成可坍缩的形状,接着读下一个。
栈式消除 · 读入与坍缩:读入第 11 个 “#”(空节点)压栈。栈顶只有一个 #,还差一个,先不坍缩,继续读。
栈式消除 · 读入与坍缩:读入第 12 个 “#”(空节点)压栈,这下栈顶正好是「真值, #, #」,可以坍缩了。
栈式消除 · 读入与坍缩:“6” 的左右孩子都到齐了,以它为根的小子树整体退化成一个空位,把栈顶三个 6, #, # 压成一个 #。坍缩后栈顶又凑出「真值, #, #」,继续往下坍。
栈式消除 · 读入与坍缩:“2” 的左右孩子都到齐了,以它为根的小子树整体退化成一个空位,把栈顶三个 2, #, # 压成一个 #。坍缩后栈顶又凑出「真值, #, #」,继续往下坍。
栈式消除 · 读入与坍缩:“9” 的左右孩子都到齐了,以它为根的小子树整体退化成一个空位,把栈顶三个 9, #, # 压成一个 #。坍缩后栈只剩一个 #,进入终局判定。
栈式消除 · 读入与坍缩:13 个记号全部读完,栈里恰好只剩一个 “#”。这说明整串正好拼成一棵完整二叉树的前序序列化,合法,返回 true,正是示例 1 的答案。
边界先想清:单个 # 合法;真节点缺一个孩子不合法;树完成后还有记号不合法。
认出「把完成的子树折叠成空位」这个母题,栈法和槽位法就都是它的化身。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class Solution: def isValidSerialization(self, preorder: str) -> bool: stk = [] for c in preorder.split(","): stk.append(c) while len(stk) > 2 and stk[-1] == stk[-2] == "#" and stk[-3] != "#": stk = stk[:-3] stk.append("#") return len(stk) == 1 and stk[0] == "#"复杂度
- 时间·整体思路:O(n),每个记号压栈一次,坍缩总次数也不超过记号数,本身是线性的
- 时间·分语言:C++/Java O(n),C++/Java 原地弹栈是 O(1),整体 O(n);Python 这版用切片 stk[-3:] 重建前缀,最坏退化到 n 的平方,想保证 O(n) 可改成 del stk[-3:] 或连续 pop 三次再 append
- 空间:O(n),最坏情况栈里同时存近 n 个记号
易错点
面试追问把动画讲成自己的话
追问除了栈,还有更省空间的做法吗?
追问为什么不用真的重建树?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
寻找二叉树的叶子节点
LeetCode 366 · 中等 · 沿着 树套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题