175 lines
4.6 KiB
Markdown
175 lines
4.6 KiB
Markdown
---
|
||
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>
|