【C/C++】919. 完全二叉树插入器

404 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情


题目链接:919. 完全二叉树插入器

题目描述

完全二叉树 是每一层(除最后一层外)都是完全填充(即,节点数达到最大)的,并且所有的节点都尽可能地集中在左侧。

设计一种算法,将一个新节点插入到一个完整的二叉树中,并在插入后保持其完整。

实现 CBTInserter 类:

  • CBTInserter(TreeNode root) 使用头节点为 root 的给定树初始化该数据结构;
  • CBTInserter.insert(int v)  向树中插入一个值为 Node.val == val 的新节点 TreeNode。使树保持完全二叉树的状态,并返回插入节点 TreeNode 的父节点的值
  • CBTInserter.get_root() 将返回树的头节点。

提示:

  • 树中节点数量范围为 [1, 1000]
  • 0Node.val50000 \leqslant Node.val \leqslant 5000
  • root 是完全二叉树
  • 0val50000 \leqslant val \leqslant 5000
  • 每个测试用例最多调用 insert 和 get_root 操作 10410^4 次

示例 1:

lc-treeinsert.jpg

输入
["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]

整理题意

题目给定一颗完全二叉树的根节点 root,并且不断向这颗完全二叉树上插入节点,要求在插入节点后需要保持完全二叉树的状态,并且可以随时返回这颗完全二叉树的根节点。

解题思路分析

根据题目给定的完全二叉树特性和示例图,我们可以得知一颗完全形成的过程就是从上到下从左到右不断添加元素节点,换句话说也就是一层一层的添加元素节点,那么我们可以由层序遍历来搜索给定的完全二叉树,同时层序遍历的时候也是从左到右的遍历,依次将还能插入元素的节点放入一个队列中。

当需要向完全二叉树中插入元素时只需要向队列的头元素节点进行插入即可,如果插入后头元素节点没有位置可以插入了就将头元素弹出。

需要注意的是在插入元素的同时也要维护队列,因为插入了一个新的元素,那么可插入的元素节点增加了,此时将这个新的元素节点放入队尾即可。

具体实现

  1. 首先层次遍历给定的完全二叉树,将可插入的节点依次放入可插入的队列中。
  2. 记录完全二叉树的根节点,当执行 CBTInserter.get_root() 函数时直接返回这个根节点即可。
  3. 插入元素时可以直接以队头元素作为父亲节点进行插入儿子节点并返回父亲节点的值,如果插入节点后队头元素没有位置可以插入元素了,那么就直接弹出即可。
  4. 需要注意插入新的儿子节点后,要将新的儿子节点入队,因为新的儿子节点也是可以作为父亲节点插入节点的。

复杂度分析

  • 时间复杂度:O(n)O(n),初始化函数 CBTInserter(TreeNode* root) 需要 O(n)O(n) 的时间,其中 n 是给定的初始完全二叉树的节点个数。插入函数 insert(int val) 和返回根节点函数 get_root() 的时间复杂度均为 O(1)O(1)
  • 空间复杂度:O(n+q)O(n + q),其中 n 是给定的初始完全二叉树的节点个数,qinsert(int val) 的调用次数。在调用了 qinsert(int val) 后,完全二叉树中有 n + q 个节点,其中有一半的节点在队列中,需要 O(n+q)O(n + q) 的空间。

代码实现

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class CBTInserter {
    // que 队头表示下一个插入的位置
    queue<TreeNode*> que;
    TreeNode *root;
public:
    CBTInserter(TreeNode* r) {
        root = r;
        while(que.size()) que.pop();
        // bfs 广搜初始化 que 队列
        queue<TreeNode*> q;
        while(q.size()) q.pop();
        q.push(root);
        while(q.size()){
            TreeNode *now = q.front(); q.pop();
            // 如果当前节点还有位置可以插入
            if(!(now->left && now->right)) que.push(now);
            // 还可写成 if(!(now->left && now->right))
            if(now->left) q.push(now->left);
            if(now->right) q.push(now->right);
        }
    }
    
    int insert(int val) {
        TreeNode *res = que.front();
        TreeNode *now = new TreeNode(val);
        // 如果队头元素左边有位置
        if(res->left == NULL){
            res->left = now;
        }
        // 否则右边有位置
        else{
            res->right = now;
            // 插入后队头元素没有位置,弹出
            que.pop();
        }
        // 将新的节点放入可插入队列
        que.push(now);
        return res->val;
    }
    
    TreeNode* get_root() {
        return root;
    }
};

/**
 * Your CBTInserter object will be instantiated and called as such:
 * CBTInserter* obj = new CBTInserter(root);
 * int param_1 = obj->insert(val);
 * TreeNode* param_2 = obj->get_root();
 */

总结

  • 该题的核心思想是 队列,其次需要使用到广度优先搜索来到达层序遍历的目的,在使用广度优先搜索时也需要使用到队列,所以该题的核心数据结构为队列。
  • 遇到树和图的题目通常都需要使用到遍历,根据不同的需求来选择深搜还是广搜。该题根据完全二叉树的节点插入顺序可以得知使用广搜来完成更方便。
  • 该题还有另外一种 二进制表示 的解法。
  • 测试结果:

919.png

结束语

努力提升自己,永远比仰望别人更有意义。自我的蜕变,正是从养成好习惯开始的。早睡早起、运动锻炼、读书写字,每一个好习惯,或许在当下看来微不足道,但只要把一点一滴累积起来,你就会离梦想越来越近。新的一天,加油!