今天刷到一个题我感觉收获真是太大了
题目如下
关键点1:位运算与二叉树结合
这个题目最传统的做法,就是递归统计节点数目,最后时间复杂度为O(n)。
但是题目给出条件是完全二叉树,这样我们就可以利用平衡二叉树的性质来优化了
首先观察我们可以发现我们要求的是最后一层的最后一个节点的值,我们可以很容易得到树的左边和右边的高度,这样就能把最后一个节点的之确定在这个范围里。然后再通过二分法不断缩小范围。关键在于,我们如何确定一个值是否再这颗树上
这就是核心思想了,二叉树和二进制位运算的关系。我们可以发现,根节点是n,那么它的左孩子的值肯定是2n,右孩子为2n+1。相当于左孩子是在根节点的二进制数后面加了个0,而右孩子是在二进制数后面加了个1
。在二叉树上就是位为0向左走,为1向右走,这样的话我们只要根据待检测值从左到右的二进制位来对这个树从根节点进行搜索,最终就可以判断这个值是否在树上了
代码如下
class Solution {
public:
bool search(TreeNode *root, int val, int level){
int bits = 1<<(level-2);
TreeNode *cur = root;
while(bits>0){
if(bits&val){
//r
cur=cur->right;
}else{
cur=cur->left;
}
bits>>=1;
}
return cur!=nullptr;
}
int countNodes(TreeNode* root) {
// return f(root);
int depth = 0;
TreeNode *cur = root;
while(cur!=nullptr){
depth++;
cur=cur->left;
}
//利用二分法在l-r里面找到最后一个节点
int l=1<<(depth-1),r=(1<<depth) -1;
while(r>l){
int m=(r-l+1)/2 + l;
if(search(root, m, depth)){
l = m;
}else{
r=m-1;
}
}
return l;
}
};
关键点二:二分法的模板
while(r>l){
int m=(r-l+1)/2 + l;
if(search(root, m, depth)){
l = m;
}else{
r=m-1;
}
}
这个模板是非常优秀的,而且能有效防止溢出。我觉得以后写二分法都可以用这个模板,要不然写二分法的题对边界条件的判断可真是一件恶心的事
这个题还有一种很妙的解法,也是O(logn)级别的,就是将树不断二分,因为原树是一个完全二叉树,所以它的根节点的左右子树至少有一个是满二叉树,另外一个也是完全二叉树。根据这个规律可以写出如下代码
class Solution {
public:
int countNodes(TreeNode* root) {
if(root==nullptr){
return 0;
}
int ld = 0, rd = 0;
TreeNode *lcur = root, *rcur = root;
while(lcur!=nullptr&& rcur!=nullptr){
ld++;
rd++;
lcur=lcur->left;
rcur = rcur->right;
}
if(lcur!=nullptr &&rcur==nullptr){
ld++;
}
if(ld == rd){
return (1<<ld) - 1;
}
return 1+countNodes(root->left)+countNodes(root->right);
}
};