zoukankan      html  css  js  c++  java
  • LeetCode——919.完全二叉树插入器

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

    设计一个用完全二叉树初始化的数据结构 CBTInserter,它支持以下几种操作:

    CBTInserter(TreeNode root) 使用头结点为 root 的给定树初始化该数据结构;
    CBTInserter.insert(int v) 将 TreeNode 插入到存在值为 node.val = v 的树中以使其保持完全二叉树的状态,并返回插入的 TreeNode 的父结点的值;
    CBTInserter.get_root() 将返回树的头结点。

    示例 1:
    
    输入:inputs = ["CBTInserter","insert","get_root"], inputs = [[[1]],[2],[]]
    输出:[null,1,[1,2]]
    
    示例 2:
    
    输入:inputs = ["CBTInserter","insert","insert","get_root"], inputs = [[[1,2,3,4,5,6]],[7],[8],[]]
    输出:[null,3,4,[1,2,3,4,5,6,7,8]]
    

    提示:

    最初给定的树是完全二叉树,且包含 1 到 1000 个结点。
    每个测试用例最多调用 CBTInserter.insert 操作 10000 次。
    给定结点或插入结点的每个值都在 0 到 5000 之间。

    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/complete-binary-tree-inserter

    首先需要搞清楚的是完全二叉树的定义,即对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,换句话说,完全二叉树从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐。由于插入操作要找到最后一层的第一个空缺的位置,所以很自然的就想到了使用层序遍历的方法,由于插入函数返回的是插入位置的父结点,所以在层序遍历的时候,只要遇到某个结点的左子结点或者右子结点不存在,则跳出循环,则这个残缺的父结点刚好就在队列的首位置。那么在插入函数时,只要取出这个残缺的父结点,判断若其左子结点不存在,说明新的结点要连接在左子结点上,否则将新的结点连接在右子结点上,并把此时的左右子结点都存入队列中,并将之前的队首元素移除队列即可,参见代码如下:

    解法一:

    C++

    class CBTInserter {
    public:
        CBTInserter(TreeNode* root) {
            tree_root = root;
            q.push(root);
            while (!q.empty()) {
                auto t = q.front(); 
                if (!t->left || !t->right) break;
                q.push(t->left);
                q.push(t->right);
                q.pop();
            }
        }   
        int insert(int v) {
            TreeNode *node = new TreeNode(v);
            auto t = q.front(); 
            if (!t->left) t->left = node;
            else {
                t->right = node;
                q.push(t->left);
                q.push(t->right);
                q.pop();
            }
            return t->val;
        }  
        TreeNode* get_root() {
            return tree_root;
        }
    
    private:
        TreeNode *tree_root;
        queue<TreeNode*> q;
    };
    

    java

    class CBTInserter {
    
        TreeNode root;
        Queue<TreeNode> q;
    
        public CBTInserter(TreeNode root) {
            this.root = root;
            q = new LinkedList();
             q.offer(root);
            while(!q.isEmpty()){
                TreeNode t = q.peek();
                if(t.left == null || t.right == null) break;
                q.offer(t.left);
                q.offer(t.right);
                q.poll();
            }
        }
        
        public int insert(int v) {
            TreeNode node = new TreeNode(v);
            TreeNode t = q.peek();
            if(t.left == null) t.left =node;
            else {
                t.right = node;
                q.offer(t.left);
                q.offer(t.right);
                q.poll();
            }
            return t.val;
        }
        
        public TreeNode get_root() {
            return root;
        }
    }
    

    下面这种解法缩短了建树的时间,但是极大的增加了插入函数的运行时间,因为每插入一个结点,都要从头开始再遍历一次,并不是很高效,可以当作一种发散思维吧,参见代码如下:

    解法二:

    class CBTInserter {
    public:
        CBTInserter(TreeNode* root) {
            tree_root = root;
        }
        int insert(int v) {
            queue<TreeNode*> q{{tree_root}};
            TreeNode *node = new TreeNode(v);
            while (!q.empty()) {
                auto t = q.front(); q.pop();
                if (t->left) q.push(t->left);
                else {
                    t->left = node;
                    return t->val;
                }
                if (t->right) q.push(t->right);
                else {
                    t->right = node;
                    return t->val;
                }
            }
            return 0;
            
        }    
        TreeNode* get_root() {
            return tree_root;
        }
    
    private:
        TreeNode *tree_root;
    };
    

    再来看一种不使用队列的解法,因为队列总是要遍历,比较麻烦,如果使用数组来按层序遍历的顺序保存这个完全二叉树的结点,将会变得十分的简单。而且有个最大的好处是,可以直接通过坐标定位到其父结点的位置,通过 (i-1)/2 来找到父结点,这样的话就完美的解决了插入函数要求返回父结点的要求,而且通过判断当前完整二叉树结点个数的奇偶,可以得知最后一个结点是在左子结点上还是右子结点上,这样就可以直接将新加入的结点连到到父结点的正确的子结点位置,参见代码如下:

    解法三:

    class CBTInserter {
    public:
        CBTInserter(TreeNode* root) {
            tree.push_back(root);
            for (int i = 0; i < tree.size(); ++i) {
                if (tree[i]->left) tree.push_back(tree[i]->left);
                if (tree[i]->right) tree.push_back(tree[i]->right);
            }
        }
        int insert(int v) {
            TreeNode *node = new TreeNode(v);
            int n = tree.size();
            tree.push_back(node);
            if (n % 2 == 1) tree[(n - 1) / 2]->left = node;
            else tree[(n - 1) / 2]->right = node;
            return tree[(n - 1) / 2]->val;
        }    
        TreeNode* get_root() {
            return tree[0];
        }
    
    private:
        vector<TreeNode*> tree;
    };
    

    方法 1:双端队列

    想法

    将所有节点编号,按照从上到下从左到右的顺序。

    在每个插入步骤中,我们希望插入到一个编号最小的节点(这样有 0 或者 1 个孩子)。

    通过维护一个 deque (双端队列),保存这些节点的编号,我们可以解决这个问题。插入一个节点之后,将成为最高编号的节点,并且没有孩子,所以插入到队列的后端。为了找到最小数字的节点,我们从队列前端弹出元素。

    算法

    首先,通过广度优先搜索将 deque 中插入含有 0 个或者 1 个孩子的节点编号。

    然后插入节点,父亲是 deque 的第一个元素,我们将新节点加入我们的 deque。

    java

    class CBTInserter {
        TreeNode root;
        Deque<TreeNode> deque;
        public CBTInserter(TreeNode root) {
            this.root = root;
            deque = new LinkedList();
            Queue<TreeNode> queue = new LinkedList();
            queue.offer(root);
    
            // BFS to populate deque
            while (!queue.isEmpty()) {
                TreeNode node = queue.poll();
                if (node.left == null || node.right == null)
                    deque.offerLast(node);
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
            }
        }
    
        public int insert(int v) {
            TreeNode node = deque.peekFirst();
            deque.offerLast(new TreeNode(v));
            if (node.left == null)
                node.left = deque.peekLast();
            else {
                node.right = deque.peekLast();
                deque.pollFirst();
            }
    
            return node.val;
        }
    
        public TreeNode get_root() {
            return root;
        }
    }
    

    Python

    class CBTInserter(object):
        def __init__(self, root):
            self.deque = collections.deque()
            self.root = root
            q = collections.deque([root])
            while q:
                node = q.popleft()
                if not node.left or not node.right:
                    self.deque.append(node)
                if node.left:
                    q.append(node.left)
                if node.right:
                    q.append(node.right)
    
        def insert(self, v):
            node = self.deque[0]
            self.deque.append(TreeNode(v))
            if not node.left:
                node.left = self.deque[-1]
            else:
                node.right = self.deque[-1]
                self.deque.popleft()
            return node.val
    
        def get_root(self):
            return self.root
    

    复杂度分析

    时间复杂度:预处理 O(N),其中 N 是树上节点编号。每个插入步骤是 O(1)。
    空间复杂度:O(N_cur),其中当前插入操作树的大小为 N_cur

    完全二叉树的特性,节点位置与数组下标映射关系:
    若节点位置位k, 则lchild位置位2k, rchild位置位2k+1; (从1开始计算)

    java

    class CBTInserter {
        int size;
        TreeNode[] A;
        void traverse(TreeNode root,int k){
            if(root==null)return;
            A[k]=root;
            if(k>size){
                size=k;
            }
            traverse(root.left,k<<1);
            traverse(root.right,(k<<1) | 1);
        }
        public CBTInserter(TreeNode root) {
            A=new TreeNode[11004];
            size=0;
            traverse(root,1);
        }
    
        public int insert(int v) {
            A[++size]=new TreeNode(v);
            if(size%2 == 0){
                A[size/2].left=A[size];
            }
            else{
                A[size/2].right=A[size];
            }
            return A[size/2].val;
        }
    
        public TreeNode get_root() {
            return A[1];
        }
    }
    
  • 相关阅读:
    权限系统概要(收集,整理)
    全程参观 Google 总部
    从Excel表格中坐标生成 点线面
    Cross Site Script 知识
    UserControl 实现From_Close
    用后立即调用Dispose
    获取鼠标点击处的颜色
    arcsde9.3 the arcsde repository is not successfully created
    ArcGIS9.3全套 下载地址
    SQLServer 2008 安装 is not a volid login or don't have permission
  • 原文地址:https://www.cnblogs.com/wwj99/p/12298419.html
Copyright © 2011-2022 走看看