LeetCode 919. 完全二叉树插入器
题目描述


题意分析
初始是一棵非空完全二叉树。每次插入都要继续保持从上到下、从左到右填满的形状,并返回新节点的父节点值;
get_root返回当前树的根,根节点身份不会因插入而改变。
解法:BFS 队列维护候选父节点
核心思路
[!blue]
完全二叉树的下一个空位,就是层序顺序中第一个尚未填上的孩子位置。如果每次插入都从根重新寻找会重复遍历,可以直接保留「按层序排列、尚未拥有两个孩子」的候选父节点队列。
构造时做一次从左到右的层序遍历,遇到左孩子或右孩子为空的节点就加入
candidates。队首因此是最浅、最靠左的未满节点,正是下次插入应选择的父节点。已填满的节点不再有插入机会,无需保留在候选队列中。插入时取队首:若左孩子为空,先填左边,父节点还缺右孩子,继续留在队首;否则它只可能缺右孩子,填上后两个位置都满了,将父节点出队。初始树完全、每次又按这个顺序插入,所以不会出现需要先填右边的情况。
新节点没有孩子,将来也是候选父节点。它刚插入当前层序的最后位置,因此追加到队尾就能维持所有候选的层序顺序。每次只调整队首和队尾,下一次仍能直接取得正确父节点,不必重新遍历或排序。
解题步骤
- 构造时层序扫描,收集孩子尚未满两个的节点。
- 插入时取候选队首作为父节点。
- 左空填左,否则填右并移除已满父节点。
- 将新节点放入队尾,返回父节点值。
初始只有根节点时,它就是唯一候选,同样按先左后右处理。有限非空二叉树总有叶子,且每次插入都会加入新候选,所以插入时队列不会为空。
get_root直接返回保存的根指针,即可看到已经插入的新节点。
代码实现
class CBTInserter {
private final TreeNode root;
private final ArrayDeque<TreeNode> candidates = new ArrayDeque<>();
public CBTInserter(TreeNode root) {
this.root = root;
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
if (node.left == null || node.right == null) {
candidates.offer(node);
}
}
}
public int insert(int val) {
TreeNode parent = candidates.peek();
TreeNode child = new TreeNode(val);
// 先填左侧;父节点只有在右侧也填满后才退出候选队列。
if (parent.left == null) {
parent.left = child;
} else {
parent.right = child;
candidates.poll();
}
// 新节点尚无孩子,按层序放到未来候选的队尾。
candidates.offer(child);
return parent.val;
}
public TreeNode get_root() {
return root;
}
}
type CBTInserter struct {
root *TreeNode
candidates []*TreeNode
}
func Constructor(root *TreeNode) CBTInserter {
q := []*TreeNode{
root,
}
candidates := make([]*TreeNode, 0)
for len(q) > 0 {
node := q[0]
q = q[1:]
if node.Left != nil {
q = append(q, node.Left)
}
if node.Right != nil {
q = append(q, node.Right)
}
if node.Left == nil || node.Right == nil {
candidates = append(candidates, node)
}
}
return CBTInserter{root: root, candidates: candidates}
}
func (c *CBTInserter) Insert(val int) int {
parent := c.candidates[0]
child := &TreeNode{Val: val}
// 先填左侧;父节点只有在右侧也填满后才退出候选队列。
if parent.Left == nil {
parent.Left = child
} else {
parent.Right = child
c.candidates = c.candidates[1:]
}
// 新节点尚无孩子,按层序放到未来候选的队尾。
c.candidates = append(c.candidates, child)
return parent.Val
}
func (c *CBTInserter) Get_root() *TreeNode {
return c.root
}
复杂度分析
- 时间复杂度:初始构造 $O(n)$;insert 均摊 $O(1)$,单次扩容最坏可能 $O(N)$,N 为当前节点数;get_root 为 $O(1)$。
- 空间复杂度:$O(n+q)$,n 为初始节点数,q 为新增节点数,候选队列随当前树增长。
关键点总结
[!green]
- 队首是层序最早的未满节点。
- 填左不出队,填右才出队。
- 新节点也要登记为未来的候选父节点。
易错点总结
[!yellow]
- 填左后立即出队:右侧空位会被跳过。
- 填右后不出队:下一次可能覆盖已有右孩子。
- 只收集叶子作为候选:遗漏已有左孩子但缺右孩子的节点。
- 新节点不入队:下一层没有可用父节点记录。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 958. 二叉树的完全性检验 | 中等 | 同样利用完全二叉树层序空位必须集中在末尾的性质,原题验证结构,本题维护下一插入位置。 |
| 102. 二叉树的层序遍历 | 中等 | 层序遍历可建立尚未满的父节点队列,让每次插入无需重新遍历。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!