Files
leetcode-go/笔试/微派 Test1/12-单选题10-树后序遍历.md
T

175 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [笔试, 微派, 树, 二叉搜索树, DP, Go]
create time: 2026-05-16 15:30
---
# 12 - 单选题 10:判断是否为 BST 的后序遍历
## 题目
给定一个整数数组,判断它是否是一个**二叉搜索树(BST)**的**后序遍历**结果。例如 `[1, 3, 2, 5, 7, 6, 4]` 是否是某个 BST 的后序遍历?
| 选项 | 内容 |
|------|------|
| A | 是,因为数组中数字不重复且可以构成 BST |
| B | 否,因为后序遍历不能唯一确定一棵 BST |
| C | 是,符合 BST 后序遍历的规则:右子树根之前的所有节点分为两组——小于根的在左子树侧,大于根的在右子树侧 |
| D | 无法判断,需要同时给出前序和中序才能判断 |
<details>
<summary>点击查看答案与解析</summary>
### ✅ 正确答案:**C**
---
### 详细解析
#### BST 后序遍历的特性
```
后序遍历顺序: [左子树] → [右子树] → [根]
└───── a ─────┘└───── b ─────┘└─ root ─┘
BST 性质: 左子树所有节点 < root < 右子树所有节点
所以合法的 BST 后序遍历必须满足:
存在分界点 k,使得:
· a[0...k-1] < root (全部小于根 → 左子树)
· a[k...n-2] > root (全部大于根 → 右子树)
```
#### 对 `[1, 3, 2, 5, 7, 6, 4]` 的分析
```
根 = 4(最后一个元素)
扫描前面: 1, 3, 2, 5, 7, 6
↑ ↑ ↑ ↑ ↑ ↑
左 左 左 右 右 右
左子树部分 [1, 3, 2] → 全部 < 4 ✓
右子树部分 [5, 7, 6] → 全部 > 4 ✓
继续递归:
左子树: [1, 3, 2], 根=2
[1] < 2 ✓, [] > 2 ✓ → 合法
右子树: [5, 7, 6], 根=6
[5] < 6 ✓, [7] > 6 ✓ → 合法
结论: **是**合法的 BST 后序遍历 ✓
```
#### 对应的 BST 结构
```mermaid
graph TD
R["4"] --> L["2"]
R --> RR["6"]
L --> LL["1"]
L --> LR["3"]
RR --> RL["5"]
RR --> RRR["7"]
style R fill:#a8e6cf
```
```
4
/ \
2 6
/ \ / \
1 3 5 7
后序: 1, 3, 2, 5, 7, 6, 4 ✓
```
#### 反例分析
如果输入是 `[1, 5, 3, 4, 2]`:
```
根 = 2
前面: [1, 5, 3, 4]
1 < 2 ← 应该是左子树
5 > 2 ← 但这是右子树的数
3 > 2 ← 右子树
4 > 2 ← 右子树
问题: 左子树区出现了值 1 之后紧接着出现了 5, 3, 4
而 5 > 2(不在左子树区间内),3 > 2,4 > 2
但这看起来合法? 等等...
再仔细看: 1 < 2 ✓, 但后面的 5, 3, 4 都 > 2 ✓
→ 这个也是合法的!
那换一个反例: [2, 1, 4, 3, 5]
根 = 5
[2, 1, 4, 3]: 全部 < 5 ✓ → 左子树
没有右子树部分 ✓
递归 [2, 1, 4, 3], 根 = 3
[2, 1]: 全部 < 3 ✓ → 左子树
[4]: > 3 ✓ → 右子树 ✓
看起来也合法...
真正的反例: [3, 1, 4, 2, 5]
根 = 5
[3, 1, 4, 2]: 全部 < 5 ✓
递归 [3, 1, 4, 2], 根 = 2
[3]: > 2 ✗ 应该全 ≤ 2,但 3 > 2
然而 1 < 2
出现矛盾: 左侧区域混入了 > 2 的值 3
→ 不合法! ❌
```
#### O(n) 单调栈解法(进阶)⭐
虽然递归 O(n²) 也能解题,面试中常要求 O(n) 的单调栈解法:
```go
func verifyPostorder(postorder []int) bool {
stack := make([]int, 0, len(postorder))
root := math.MaxInt64 // 代表"当前应该在右子树区域"的下界
for i := len(postorder) - 1; i >= 0; i-- {
// 如果当前值 < root,说明它出现在某个祖先节点的左子树中
// 但后续元素都必须 <= 那个祖先节点的左孩子
if postorder[i] > root {
return false
}
// 从栈顶弹出比当前值大的元素
for len(stack) > 0 && stack[len(stack)-1] > postorder[i] {
root = stack[len(stack)-1] // 记录弹出的最大值作为下界
stack = stack[:len(stack)-1]
}
stack = append(stack, postorder[i])
}
return true
}
```
> [!tip] 核心逻辑
> 从后往前遍历,相当于**反向后序**(根 → 右 → 左)。用一个栈维护路径,当遇到下降趋势时弹出,弹出的最后一个值就是当前节点的**祖先下界**,后面不能再出现比这个下界大的值。
#### 复杂度对比
| 方法 | 时间 | 空间 |
|------|------|------|
| 递归分治 | O(n²) 最坏, O(n log n) 平均 | O(h) = O(n) 最坏 |
| 单调栈 | O(n) | O(n) |
> [!question] 💡 思考题
> 如果是**前序遍历**判定 BST,思路有什么变化?
>
> **答案**: 前序的第一个元素是根,然后找第一个大于根的元素为分界点,剩余部分应该全部大于根。验证通过后递归处理左右子树。也可以用类似的单调栈方法(正向遍历)。
</details>