A complete binary tree is a binary tree in which every level, except possibly the last, is completely filled, and all nodes are as far left as possible.
Design an algorithm to insert a new node to a complete binary tree keeping it complete after the insertion.
Implement the CBTInserter
class:
CBTInserter(TreeNode root)
Initializes the data structure with the root
of the complete binary tree.int insert(int v)
Inserts a TreeNode
into the tree with value Node.val == val
so that the tree remains complete, and returns the value of the parent of the inserted TreeNode
.TreeNode get_root()
Returns the root node of the tree.Example:
Input: ["CBTInserter", "insert", "insert", "get_root"] [[[1, 2]], [3], [4], []] Output: [null, 1, 2, [1, 2, 3, 4]]
Explanation: CBTInserter cBTInserter = new CBTInserter([1, 2]); cBTInserter.insert(3); // return 1 cBTInserter.insert(4); // return 2 cBTInserter.get_root(); // return [1, 2, 3, 4]
Constraints:
[1, 1000]
.0 <= Node.val <= 5000
root
is a complete binary tree.0 <= val <= 5000
10^4
calls will be made to insert
and get_root
.# Complete Binary Tree Inserter
## Problem Description
The problem requires designing a `CBTInserter` class that can insert new nodes into a complete binary tree while maintaining its completeness. A complete binary tree is a binary tree where every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible.
The `CBTInserter` class should have the following methods:
* `CBTInserter(TreeNode root)`: Initializes the data structure with the root of the complete binary tree.
* `int insert(int v)`: Inserts a `TreeNode` with value `v` into the tree, maintaining completeness, and returns the value of the parent of the inserted node.
* `TreeNode get_root()`: Returns the root node of the tree.
## Naive Approach
A naive approach would involve traversing the tree to find the appropriate location to insert the new node. We can perform a level-order traversal to find the first node that has either a missing left child or a missing right child. Inserting the new node there ensures the tree remains complete.
## Optimal Approach
To optimize the insertion process, we can maintain a queue of nodes that are potential parents for new nodes. These are nodes that have either a missing left or right child. During insertion, we check the front of the queue. If the node at the front has a missing left child, we insert the new node as its left child. If the left child is present but the right child is missing, we insert the new node as its right child. Once a node has both left and right children, it is removed from the queue, as it is no longer a potential parent. We also need to add the new node to the queue since it might need children.
```python
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class CBTInserter:
def __init__(self, root: TreeNode):
self.root = root
self.queue = deque()
q = deque([root])
while q:
node = q.popleft()
if not node.left or not node.right:
self.queue.append(node)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
def insert(self, val: int) -> int:
node = TreeNode(val)
parent = self.queue[0]
if not parent.left:
parent.left = node
else:
parent.right = node
self.queue.popleft()
self.queue.append(node)
return parent.val
def get_root(self) -> TreeNode:
return self.root
# Your CBTInserter object will be instantiated and called as such:
# obj = CBTInserter(root)
# param_1 = obj.insert(val)
# param_2 = obj.get_root()
CBTInserter(TreeNode root)
: The constructor performs a level-order traversal of the tree, which takes O(N) time, where N is the number of nodes in the tree. The while loop processes each node once, leading to a runtime proportional to the number of nodes. Appending and popping from deque are O(1). Therefore, the overall runtime is O(N).insert(int v)
: The insert
function has a time complexity of O(1) because it performs a constant number of operations: accessing the front of the queue, creating a new node, inserting the node, and potentially removing a node from the front of the queue and appending the new node to the end. These are all constant-time operations.get_root()
: The get_root
function simply returns the root node, which takes O(1) time.CBTInserter(TreeNode root)
: The constructor uses a queue to store potential parent nodes. In the worst-case scenario, where the last level of the tree is only partially filled, the queue can contain nodes from the last two levels. In a complete binary tree, the number of nodes at the last level can be up to N/2. Thus, the space complexity of the queue can be O(N) in the worst case. Therefore, the overall space complexity is O(N).insert(int v)
: The insert
function creates a new TreeNode
, which takes O(1) space. It also adds the new node to the queue, but this does not change the overall space complexity, as the queue's size is still bounded by O(N).get_root()
: The get_root
function uses O(1) space because it only returns the root node without using any additional memory.insert
operation is O(1).