
Google VO 里经常会出现一类题:题面看起来不复杂,但非常考察你能不能先把问题抽象清楚,再决定用递归、图模型还是模拟过程去做。
这道题就是一个很典型的例子。
题意是:
给定一棵树。每次可以选择一个叶子节点并删除。要求你输出在最终删完整棵树的过程中,任意一个合法的删除序列。
换句话说,只要你的序列满足:
- 每次删掉的节点,在当时确实是叶子
- 最后所有节点都被删完
那这个序列就是合法答案。
这题一开始最重要的是先 clarify
这题本身不难,但非常适合在开场用 clarify 把边界定清楚。因为不同设定会直接影响解法表述。
这道题比较值得先确认的点有四个:
1. 是二叉树还是多叉树?
面试里最合理的处理方式通常是:
- 先问
- 如果对方没有特别指定,先按二叉树讲清楚
- 再主动补一句:多叉树思路本质一致
这样做的好处是,你既没有擅自假设,也没有把问题讲复杂。
2. 需不需要考虑输入不是合法树的情况?
如果面试官说不用,那整个问题就可以直接建立在“输入保证合法”之上,不需要额外再做环检测或连通性判断。
这种 clarify 很重要,因为它决定了你是否要把时间花在无关的 defensive programming 上。
3. 输入规模是否适合递归?
如果面试官明确说可以用递归,那就非常关键。因为这题在“只要求输出任意一个合法序列”的版本下,递归几乎就是最自然的第一解。
4. 是一次性生成完整答案,还是每次调用返回一个删除节点?
这点会直接决定你是:
- 做一个后序遍历式的整体输出
- 还是设计一个可逐步产出节点的状态机 / 迭代器
如果是一次性生成,那递归解法会非常顺。
为什么这题一旦只要求“任意一个合法序列”,就几乎等于递归题
这题最核心的约束其实只有一句话:
父节点必须在所有子节点删完之后,才能被删除。
一旦你把它翻译成这个形式,问题就立刻变得非常像树上的后序遍历。
因为后序遍历本来就是:
- 先处理子节点
- 再处理父节点
而在这道题里,“处理”刚好就可以理解为“删除并加入答案序列”。
所以从建模角度看,这题最自然的想法就是:
- 先递归删除每棵子树
- 等所有子节点都删完,当前节点自然就变成叶子
- 这时再删当前节点
这也就是为什么它会被很多人第一眼认成递归题。
递归解法应该怎么讲
如果你在面试里讲这题,最稳的方式不是上来就写代码,而是先把递归函数的职责定义清楚。
比较自然的定义是:
递归函数负责返回“删除当前子树所对应的一个合法序列”。
围绕这个定义,整个过程其实非常顺:
i. 如果当前节点是 None
那就直接返回。
因为空树不需要删除任何东西,也不会对答案产生影响。
ii. 遍历当前节点的所有子节点
对每个子节点递归地生成删除序列。
这个步骤的意义是先保证下面的子树全部被清空。
iii. 当所有子节点都处理完
从逻辑上说,这些孩子都已经被删掉了。也就是说,当前节点此时已经没有孩子了,于是它自然变成了叶子。
iv. 将当前节点加入答案序列
因为它现在已经满足“叶子节点可删”的条件,所以当前节点被加入序列就是合法的。
整套逻辑和树的后序遍历几乎完全一致。
为什么二叉树和多叉树本质上没区别
这题非常适合在讲完二叉树版本后主动补一句:
如果扩展到多叉树,思路完全一样,只是从“递归左右子树”变成“递归遍历所有孩子”。
这句话很加分,因为它说明你没有把方法绑死在二叉树结构上,而是看到了它真正依赖的只是:
- 节点到孩子的层级关系
- “孩子先于父亲删除”的偏序约束
所以从二叉树推广到多叉树时,核心模型完全不变。
这题为什么是合法的
面试里最好不要只说“像后序遍历”,还要补一句为什么正确。
证明其实很直观:
- 任意一个节点只有在所有孩子都已经删除后,才会被加入答案
- 所以它被加入时,一定已经是叶子
- 因此序列中的每一步删除都合法
最终所有节点都会恰好被访问一次并加入答案,所以整个树也一定会被删空。
这就是 correctness 的核心。
Follow-up:如果不是一次性生成,而是要一个个返回怎么办?
这是这道题最自然也最有价值的 follow-up。
因为它把问题从“静态生成一个答案”推进到了“动态地按顺序产出节点”。
这时常见有两个方向可以讲。
方向一:用图模型做“从叶子开始的拓扑排序”
这是一个非常漂亮的 follow-up 视角。
如果你把每条树边的方向反过来看,把“孩子删完父亲才能删”理解成一种依赖关系,那么:
- 当前叶子节点可以立刻输出
- 当一个叶子被删除,它对父节点的一条依赖也被消除
- 如果父节点的所有孩子都删完了,它就会变成新的可删除节点
这和拓扑排序本质上一样。
更具体地说
可以把每个节点还剩多少个“未删除孩子”看成它的入度或剩余依赖数。
初始化时:
- 所有叶子节点都可以先入队
然后每次:
- 从队列里取一个当前可删的叶子
- 输出它
- 找到它的父节点,把父节点的剩余孩子数减一
- 如果父节点的剩余孩子数变成 0,就把父节点入队
这样最终的出队顺序,就是一个合法删除序列。
这个方法为什么好
因为它特别适合“一个个返回答案”的场景。
你不一定需要一次性构造完整序列,而是可以每次从队列里弹出一个当前合法删除的节点。
这就很像把原来的离线递归方案,变成了一个在线生成器。
方向二:用栈模拟递归过程
另一种 follow-up 说法是:
如果我们不想真的用递归,也可以手动维护一个栈,去模拟后序遍历的展开过程。
这条路的本质不是换算法,而是换执行方式。
原本递归栈帮你记录:
- 当前处理到哪个节点
- 哪些孩子已经处理完
- 什么时候可以回到父节点
如果不用系统递归,就自己维护这些状态。
这种方法的好处是:
- 避免依赖语言调用栈
- 更适合在某些场景下做迭代实现
但从表达上来说,如果题目没有明确限制递归,这一版通常更适合作为 follow-up,而不是第一解。
这题真正考察的能力是什么
这题表面像树题,但实际上在考三个层次:
第一层:你能不能快速抓住偏序关系
也就是:
- 子节点必须先删
- 父节点必须后删
一旦看清这个顺序关系,后面的方法都会自然很多。
第二层:你能不能从“递归解”切换到“在线生成解”
第一解用递归通常不难。
真正拉开差距的是:当面试官把题改成“一个个生成节点”时,你能不能迅速意识到这已经更像拓扑排序或状态模拟问题。
第三层:你能不能把树和图的视角打通
这也是 Google 很喜欢看的地方。
同一题,既可以从树的后序遍历角度解释,也可以从“删除依赖”的图模型角度解释。如果你能主动切换视角,面试官通常会觉得你理解得更深。
这题在 Google VO 里为什么很舒服
因为它不是靠技巧卡人,而是很适合看候选人是不是有清晰的建模能力。
你如果只会背模板,会觉得它像普通树题。
你如果真的理解顺序约束,就会发现:
- 第一问是后序遍历
- follow-up 是叶子驱动的拓扑排序
- 再往下还能聊递归与迭代的实现差异
这就是很典型的 Google VO 风格:
原题不一定难,但 follow-up 会看你能不能把一个模型讲透。
📌 最后总结
这道题最值得记住的核心,不是“删除叶子节点”这句话本身,而是它背后的偏序关系:
- 子节点必须早于父节点
只要看到这一点:
- 一次性输出版本,自然就是树上的后序递归
- 一个个生成版本,自然可以转成叶子出发的拓扑排序
如果你最近在准备 Google VO,这类题很值得反复练。因为它特别能体现你是不是只会写代码,还是已经能把递归、图和顺序约束统一到一个模型里讲清楚。
🚀 oavoservice:你的 Google VO 稳定输出保障
Google VO 很多题真正难的都不是第一问,而是后续 follow-up 一层层往下压时,你能不能继续把模型讲清楚。
我们提供:
✅ Google VO 实时辅助 — Coding、BQ、System Design 全程支持
✅ 原题 mock — 尽可能还原真实 Google 面试节奏
✅ 高质量 follow-up 训练 — 不只练会做,更练会讲
✅ 真实面经沉淀 — 持续整理最新 Google 高频轮次
如果你也想拿真实题感做 mock,或者想在正式面试前把表达稳定住,欢迎直接来聊。
👉 立即添加微信:Coding0201
Telegram: @OAVOProxy
Gmail: [email protected]