这节课抓一个核心:每个结点只保留高度、是否满、是否完全三件事;左右子树信息已知后,当前结点就能 O(1) 合并。
对每个结点 u,后序遍历先处理左右儿子,再合并当前结点。
h[u]:以 u 为根的子树高度。
它告诉我们左右两边谁高、高多少。
full[u]:这棵子树是不是满二叉树。
完全二叉树的判断里经常需要“一边必须满”。
complete[u]:这棵子树是不是完全二叉树。
答案就是所有 complete[u] 为真的结点个数。
完全二叉树只关心形状:最后一层必须从左到右连续出现。判断当前结点时,不需要重新扫整棵子树;只要知道左右子树是否满、是否完全、以及高度,就能确认有没有“中间空洞”。
高度 + 满 + 完全,不会丢失判断当前形状所需的信息。点“下一步”按后序顺序合并。蓝色是已经处理好的子树,绿色是当前结点,虚线是空儿子。
| 位置 | h | full | complete |
|---|
1 ├── 2 │ └── 4 └── 3
四个结点的子树都是完全二叉树,答案是 4。
1
├── 2
└── 3
└── 4
结点 3 的子树不是完全二叉树,其余三个结点是,答案是 3。
full_tree[0] 和 complete_tree[0] 必须设为 true。dfs(lch[u]),再 dfs(rch[u]),最后算 u。complete_tree[u] 为真就加一。#include <bits/stdc++.h>
using namespace std;
const int N = 100000 + 5;
int lch[N], rch[N];
int h[N];
bool full_tree[N], complete_tree[N];
int ans = 0;
void dfs(int u) {
if (u == 0) return;
dfs(lch[u]);
dfs(rch[u]);
int L = lch[u], R = rch[u];
int hl = h[L], hr = h[R];
h[u] = max(hl, hr) + 1;
full_tree[u] = full_tree[L] && full_tree[R] && (hl == hr);
complete_tree[u] =
(complete_tree[L] && full_tree[R] && hl == hr + 1) ||
(full_tree[L] && complete_tree[R] && hl == hr);
if (complete_tree[u]) ans++;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> lch[i] >> rch[i];
}
h[0] = 0;
full_tree[0] = true;
complete_tree[0] = true;
dfs(1);
cout << ans << '\n';
return 0;
}
O(n),空间 O(n)。