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>
|