Given a complete binary tree, count the number of nodes.
Definition of a complete binary tree from Wikipedia:
In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h.
计算一个完全二叉树的结点数量,关于完全二叉树的定义,维基百科上是这样写的,
In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible. It can have between 1 and 2h-1 nodes at the last level h.[19] An alternative definition is a perfect tree whose rightmost leaves (perhaps all) have been removed. Some authors use the term complete to refer instead to a perfect binary tree as defined above, in which case they call this type of tree an almost complete binary tree or nearly complete binary tree.[20][21] A complete binary tree can be efficiently represented using an array.[19]
一个二叉树,除了最后一层外,其它层的每个结点均具有两个孩子结点,最后一层的结点是从左往右排列的,这样的二叉树就是一个完全二叉树。
这个问题的难点在于如何找到最后一层有多少个结点,我们可以计算出树的高度为h,那么总结点数量=2^(h - 1) - 1 + 最后一层结点数。想要到最后一层,必须从根结点往下找,那么比较直接的一种想法,就是直接二叉树,求出所有的结点,这样子虽然比较简单,但是显然不是这个题的目的,要不然给出一个complete binary tree就没有意义了。
姑且将最后一层的从左往右的最后一个点称为断点。
首先我们先将数的深度求出h
要找到断点的位置,并且不是遍历树,则不能是层遍历,只能是深度遍历,最容易想到的是从根结点开始,按照前序的顺序,沿着一条路径到根结点,并查看这个根结点的深度是否为h,如果它的深度为h,则说明它不是断点,非断点个数count加一,如果它的深度为h - 1,则说明它是一个断点,这个时候计算结果2^(h - 1) - 1 + count,返回就完成了。
代码如下:(但是这道题扣时间太紧了,导致这个方法直接TLE)
int countNodes(TreeNode* root)
{
if (!root)return 0;
stack<TreeNode*> stk;
stack<int> stk_int;
int layer = 0, cur_layer = 1, last_layer_count = 0;
TreeNode* cur = root;
while (true)
{
while (cur)
{
if (cur->right) {
stk.push(cur->right);
stk_int.push(cur_layer + 1);
}
cur = cur->left;
cur_layer++;
}
cur_layer--;
if (cur_layer < layer)break;
if (cur_layer > layer)layer = cur_layer;
last_layer_count++;
if (stk.empty())break;
cur = stk.top();
cur_layer = stk_int.top();
stk.pop(); stk_int.pop();
}
return pow(2, layer - 1) - 1 + last_layer_count;
}
那么这种方法挂了,为什么呢,猜测是因为对于非常接近完美二叉树的测试数据,用时和直接遍历二叉树差不多,平均时间复杂度为O(2^(n - 1))。n为树的深度,显然太大了。
换一种思路,对与根结点来说,这棵树的高度为h,如果说它的左子树的高度为h - 1,右子树的高度也为h - 1,这说明断点在右子树里,那么完全二叉树的结点个数就等于左子树的结点个数+1+右子树的结点个数,也就是2^(h - 1) - 1 + 1 + 右子树的结点个数,同样的,如果右子树的高度为h - 2,那么总结点个数就是2^(h - 2) - 1 + 1 + 左子树的结点个数,这么一想,解决方案就变成了一个递归的解。
int height(TreeNode* root)
{
if (!root)return 0;
return 1 + height(root->left);
}
int countNodes(TreeNode* root)
{
if (!root)return 0;
if(!root->left && !root->right)return 1;
int left_height = height(root->left);
int right_height = height(root->right);
if (left_height == right_height)return (1 << left_height) + countNodes(root->right);
return countNodes(root->left) + (1 << right_height);
}
时间复杂度为O(log n),n为总结点个数,将n转换为树高度,那么时间复杂度就为O(log 2^n) = O(n),n为树的高度。