LeetCode 1361. 验证二叉树
题目描述



题意分析
节点编号为
0..n-1,两个数组分别给出每个节点的左、右孩子编号,负一表示该孩子不存在。判断这些关系是否将全部n个节点恰好组成一棵合法二叉树。输入只是候选关系,并不保证已经是树:可能一个孩子被引用多次、存在环,或分成互不连通的部分。二叉限制每个节点最多两个孩子,而不是允许一个节点有两个父节点;根也不一定是编号零。
解法:入度 + BFS 判树
核心思路
[!blue]
合法有根树需要三个条件:根没有父引用,其余节点各有一个父引用,而且从根能到达全部节点。左右孩子数组已经限制了每个节点最多两个孩子,接下来只需验证父引用和整体连接关系。
先统计入度,也就是每个节点被当作孩子引用的次数。只要大于一就失败,即使两次引用来自同一个父节点的左右位置也不行。再寻找入度为零的节点,必须恰好有一个,才能作为唯一根;没有根或有多个根都不能构成单棵树。
这些检查仍不能排除与根无关的独立环,因为环上每个节点的入度也可以恰好为一。因此还要从唯一根遍历,确认可达节点数等于
n,不能在找到唯一根后直接成功。在前面已保证入度至多一、根入度为零的前提下,根可达部分不会出现重复访问:两条不同路径汇入同一节点需要两个父引用;若从根进入一个环,第一个入环节点既有环内前驱又有环外前驱,同样超过一,根自身则不能在环中。因此这份 BFS 无需额外
visited,计数不会因可达环而无限增长。若遍历覆盖全部节点,唯一根、单一父引用和无可达环就共同保证整张结构是一棵二叉树;若未覆盖,剩余节点构成独立结构,必须拒绝。
解题步骤
- 扫描所有左右孩子,跳过负一,每次增加对应入度,超过一立即返回假。
- 找到唯一入度为零的节点,多于一个或一个也没有都返回假。
- 从该根做 BFS,遇到非空孩子就入队,并统计处理的节点总数。
- 仅当可达数量等于
n时返回真。
代码实现
class Solution {
public boolean validateBinaryTreeNodes(int n, int[] leftChild, int[] rightChild) {
int[] indeg = new int[n];
for (int i = 0; i < n; i++) {
// 同一孩子被重复引用,不能构成树。
if (leftChild[i] != -1 && ++indeg[leftChild[i]] > 1) {
return false;
}
if (rightChild[i] != -1 && ++indeg[rightChild[i]] > 1) {
return false;
}
}
int root = -1;
for (int i = 0; i < n; i++) {
if (indeg[i] == 0) {
// 出现多个候选根,不能构成单棵树。
if (root != -1) {
return false;
}
root = i;
}
}
// 一个入度为 0 的节点都没有,说明整图含环。
if (root == -1) {
return false;
}
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(root);
int count = 0;
while (!queue.isEmpty()) {
int cur = queue.poll();
count++;
int l = leftChild[cur];
int r = rightChild[cur];
if (l != -1) {
queue.offer(l);
}
if (r != -1) {
queue.offer(r);
}
}
// 存在根可达不到的节点,说明另有独立的环或子结构。
return count == n;
}
}
func validateBinaryTreeNodes(n int, leftChild []int, rightChild []int) bool {
// 统计父引用次数,同一孩子不能被重复引用。
indeg := make([]int, n)
for i := 0; i < n; i++ {
if leftChild[i] != -1 {
indeg[leftChild[i]]++
if indeg[leftChild[i]] > 1 {
return false
}
}
if rightChild[i] != -1 {
indeg[rightChild[i]]++
if indeg[rightChild[i]] > 1 {
return false
}
}
}
root := -1
for i, degree := range indeg {
if degree == 0 {
// 已经存在候选根,再出现一个就不能构成单棵树。
if root != -1 {
return false
}
root = i
}
}
// 没有根的有限结构含环,不能构成树。
if root == -1 {
return false
}
queue := []int{
root,
}
for head := 0; head < len(queue); head++ {
cur := queue[head]
if leftChild[cur] != -1 {
queue = append(queue, leftChild[cur])
}
if rightChild[cur] != -1 {
queue = append(queue, rightChild[cur])
}
}
// 根唯一仍可能有独立环,必须从根访问全部节点。
return len(queue) == n
}
复杂度分析
- 时间复杂度:$O(n)$,统计入度、找根和遍历各为线性。
- 空间复杂度:$O(n)$,保存入度和遍历队列。
关键点总结
[!green]
- 统计的是父引用次数,同一父节点两次指向同一孩子也不合法。
- 根唯一只是必要条件,独立环可能通过入度检查,仍需验证全部节点可达。
- 不使用
visited依赖前面的入度与根检查,不能把这段遍历直接用于任意有向图。- 孩子为负一表示缺失,不是可以访问的节点编号。
易错点总结
[!yellow]
- 假设节点 0 一定是根:输入的真实根可能是其他编号。
- 不检查连通性:会放过独立环与合法树并存的情况。
- 二叉理解成最多两个父节点:二叉限制孩子数,每个非根仍只能一个父节点。
- 忽略 -1 哨兵:会访问非法下标。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 261. 以图判树 | 中等 | 原题无向图检查连通与环即可,本题还需检查每个节点至多一个父亲并且根唯一。 |
| 685. 冗余连接 II | 困难 | 同样涉及有向树中的入度冲突和环,本题判断一般给定结构,原题只移除一条多余边。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!