Medium

题目描述

完全二叉树是一个二叉树,其中除了可能的最后一层外,每一层都被完全填充,并且所有节点都尽可能地靠左。

设计一个算法,将新节点插入到完全二叉树中,使插入后仍保持完全二叉树的性质。

实现 CBTInserter 类:

  • CBTInserter(TreeNode root) 使用完全二叉树的根节点初始化数据结构。
  • int insert(int v) 向树中插入一个值为 valTreeNode,使树保持完全,并返回被插入 TreeNode 的父节点的值。
  • TreeNode get_root() 返回树的根节点。

示例 1:

输入
["CBTInserter", "insert", "insert", "get_root"]
[[[1, 2]], [3], [4], []]
输出
[null, 1, 2, [1, 2, 3, 4]]

解释
CBTInserter cBTInserter = new CBTInserter([1, 2]);
cBTInserter.insert(3);  // 返回 1
cBTInserter.insert(4);  // 返回 2
cBTInserter.get_root(); // 返回 [1, 2, 3, 4]

提示:

  • 树中节点数量在范围 [1, 1000]
  • 0 <= Node.val <= 5000
  • root 是一个完全二叉树
  • 0 <= val <= 5000
  • 最多调用 insertget_root 操作 10^4

解题思路

解题思路

这道题的核心是维护完全二叉树的性质。完全二叉树的特点是除了最后一层外,所有层都被完全填满,最后一层的节点都靠左排列。

方法一:层序遍历维护候选父节点

  • 在构造函数中,使用BFS找到所有可能成为父节点的节点(即还有空位的节点)
  • 维护一个队列存储这些候选父节点
  • 插入时,从队列头部取出父节点,添加子节点
  • 如果父节点的两个子节点都填满了,就从队列中移除
  • 如果新插入的节点可能有子节点,将其加入队列

方法二:基于节点编号的数学方法

  • 将完全二叉树按层序遍历编号(根节点为1)
  • 维护当前节点总数,新节点的编号就是 count + 1
  • 根据编号可以直接计算出父节点位置:parent = (count + 1) / 2
  • 通过位运算或路径追踪找到插入位置

这里采用方法一,因为它更直观且易于理解。通过维护候选父节点队列,可以确保每次插入都能在O(1)时间内找到正确的插入位置。

代码实现

class CBTInserter {
private:
    TreeNode* root;
    queue<TreeNode*> candidates;
    
public:
    CBTInserter(TreeNode* root) {
        this->root = root;
        queue<TreeNode*> q;
        q.push(root);
        
        // BFS遍历,找到所有可能的父节点候选
        while (!q.empty()) {
            TreeNode* node = q.front();
            q.pop();
            
            // 如果节点还有空位,加入候选队列
            if (!node->left || !node->right) {
                candidates.push(node);
            }
            
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }
    
    int insert(int val) {
        TreeNode* newNode = new TreeNode(val);
        TreeNode* parent = candidates.front();
        
        if (!parent->left) {
            parent->left = newNode;
        } else {
            parent->right = newNode;
            // 父节点已满,移除候选
            candidates.pop();
        }
        
        // 新节点可能成为父节点
        candidates.push(newNode);
        
        return parent->val;
    }
    
    TreeNode* get_root() {
        return root;
    }
};
class CBTInserter:

    def __init__(self, root: Optional[TreeNode]):
        self.root = root
        self.candidates = collections.deque()
        
        # BFS遍历,找到所有可能的父节点候选
        queue = collections.deque([root])
        while queue:
            node = queue.popleft()
            
            # 如果节点还有空位,加入候选队列
            if not node.left or not node.right:
                self.candidates.append(node)
            
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

    def insert(self, val: int) -> int:
        new_node = TreeNode(val)
        parent = self.candidates[0]
        
        if not parent.left:
            parent.left = new_node
        else:
            parent.right = new_node
            # 父节点已满,移除候选
            self.candidates.popleft()
        
        # 新节点可能成为父节点
        self.candidates.append(new_node)
        
        return parent.val

    def get_root(self) -> Optional[TreeNode]:
        return self.root
public class CBTInserter {
    private TreeNode root;
    private Queue<TreeNode> candidates;

    public CBTInserter(TreeNode root) {
        this.root = root;
        this.candidates = new Queue<TreeNode>();
        
        // BFS遍历,找到所有可能的父节点候选
        Queue<TreeNode> queue = new Queue<TreeNode>();
        queue.Enqueue(root);
        
        while (queue.Count > 0) {
            TreeNode node = queue.Dequeue();
            
            // 如果节点还有空位,加入候选队列
            if (node.left == null || node.right == null) {
                candidates.Enqueue(node);
            }
            
            if (node.left != null) queue.Enqueue(node.left);
            if (node.right != null) queue.Enqueue(node.right);
        }
    }
    
    public int Insert(int val) {
        TreeNode newNode = new TreeNode(val);
        TreeNode parent = candidates.Peek();
        
        if (parent.left == null) {
            parent.left = newNode;
        } else {
            parent.right = newNode;
            // 父节点已满,移除候选
            candidates.Dequeue();
        }
        
        // 新节点可能成为父节点
        candidates.Enqueue(newNode);
        
        return parent.val;
    }
    
    public TreeNode Get_root() {
        return root;
    }
}
var CBTInserter = function(root) {
    this.root = root;
    this.candidates = [];
    
    // BFS遍历,找到所有可能的父节点候选
    const queue = [root];
    while (queue.length > 0) {
        const node = queue.shift();
        
        // 如果节点还有空位,加入候选队列
        if (!node.left || !node.right) {
            this.candidates.push(node);
        }
        
        if (node.left) queue.push(node.left);
        if (node.right) queue.push(node.right);
    }
};

CBTInserter.prototype.insert = function(val) {
    const newNode = new TreeNode(val);
    const parent = this.candidates[0];
    
    if (!parent.left) {
        parent.left = newNode;
    } else {
        parent.right = newNode;
        // 父节点已满,移除候选
        this.candidates.shift();
    }
    
    // 新节点可能成为父节点
    this.candidates.push(newNode);
    
    return parent.val;
};

CBTInserter.prototype.get_root = function() {
    return this.root;
};

复杂度分析

操作时间复杂度空间复杂度
构造函数O(n)O(n)
insertO(1)O(1)
get_rootO(1)O(1)

其中 n 是树中节点的数量。构造函数需要遍历整棵树来找到候选父节点,空间复杂度主要用于存储候选队列和BFS过程中的临时队列。