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.
Write a data structure CBTInserter
that is initialized with a complete binary tree and supports the following operations:
CBTInserter(TreeNode root)
initializes the data structure on a given tree with head noderoot
;CBTInserter.insert(int v)
will insert aTreeNode
into the tree with valuenode.val = v
so that the tree remains complete, and returns the value of the parent of the insertedTreeNode
;CBTInserter.get_root()
will return the head node of the tree.
Example 1:
Input: inputs = ["CBTInserter","insert","get_root"], inputs = [[[1]],[2],[]]
Output: [null,1,[1,2]]
Example 2:
Input: inputs = ["CBTInserter","insert","insert","get_root"], inputs = [[[1,2,3,4,5,6]],[7],[8],[]]
Output: [null,3,4,[1,2,3,4,5,6,7,8]]
Note:
- The initial given tree is complete and contains between
1
and1000
nodes. CBTInserter.insert
is called at most10000
times per test case.- Every value of a given or inserted node is between
0
and5000
.
這道題說是讓實現一個完全二叉樹的插入器的類,之前也做過關於完全二叉樹的題 [Count Complete Tree Nodes](http://www.cnblogs.com/grandyang/p/4567827.html)。首先需要搞清楚的是完全二叉樹的定義,即對於一顆二叉樹,假設其深度為d(d>1)。除了第d層外,其它各層的節點數目均已達最大值,且第d層所有節點從左向右連續地緊密排列,換句話說,完全二叉樹從根結點到倒數第二層滿足完美二叉樹,最后一層可以不完全填充,其葉子結點都靠左對齊。由於插入操作要找到最后一層的第一個空缺的位置,所以很自然的就想到了使用層序遍歷的方法,由於插入函數返回的是插入位置的父結點,所以在層序遍歷的時候,只要遇到某個結點的左子結點或者右子結點不存在,則跳出循環,則這個殘缺的父結點剛好就在隊列的首位置。那么在插入函數時,只要取出這個殘缺的父結點,判斷若其左子結點不存在,說明新的結點要連接在左子結點上,否則將新的結點連接在右子結點上,並把此時的左右子結點都存入隊列中,並將之前的隊首元素移除隊列即可,參見代碼如下:
解法一:
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;
};
下面這種解法縮短了建樹的時間,但是極大的增加了插入函數的運行時間,因為每插入一個結點,都要從頭開始再遍歷一次,並不是很高效,可以當作一種發散思維吧,參見代碼如下:
解法二:
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;
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/919
類似題目:
參考資料:
https://leetcode.com/problems/complete-binary-tree-inserter/
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)