只说O(log2N)的思路,常规DFS和BFS都是O(N)。
我思考了下,但是代码没写完。但是细细一考虑发现复杂度还是O(N),只是除了最坏情况不用遍历而已了。我的思路是先走左子树求出深度。然后按中序遍历思路,去找最下层的结点:
只要是在执行内层循环语句之后(必须进入内层循环),发现深度和原来的深度不一样,说明就走到了。最后累加即可。
但是写不出来,因为缺少一个执行完循环才会执行的语句(我印象是有,但是想不起来了,只有有这个才能判断,才能执行)。刚刚想到可以用一个bool变量判断;但是还是不行
class Solution {
public:
int countNodes(TreeNode* root) {
if(!root)
return 0;
stack<TreeNode*> s;
TreeNode *pmove=root;
int res=0;
int high=0,comp=0,k=0;
while(pmove)
{
pmove=pmove->left;
high++;
}
bool flag=false;
pmove=root;
while(!s.empty()||pmove)
{
flag=false;
while(pmove)
{
flag=true;
s.push(pmove);
pmove=pmove->left;
comp++;
}
if(flag)
{
if(comp!=high)
break;
k++;
}
pmove=s.top();
s.pop();
comp--;
pmove=pmove->right;
if(pmove)
comp++;
}
for(int i=0;i<high-1;i++)
res+=pow(2,i);
res+=k;
return res;
}
};
学习答案。
学习点:1、位运算,之前用过一次,但是经过提示还是要仔细想才能想通
2、二分法,实际上自己一直对这个理解的不透彻,决定还是先试着解释下;
首先下限low,上限high。中间的mid,到底是怎么做呢?直觉上是mid=(low+high)/2;但是这样最后会一直等于low,无法前进?
于是可以这样理解,mid=low+(high-low+1)/2;实际上也等于mid=(hig+low+1)/2;是下限加上(上限与下限之间的个数),因为是个数,所以要加1,这个很好理解因为这个个数包括了上下限,所以加1,比方说4和7之间有4,5,6,7四个数=7-4+1;
再有就是low向后推进的话,可以理解为low要替换掉mid,但是mid仍然和low是一种情况;所以mid=low;就像这道题中,mid的结点仍然和low一样,都是一直有结点的。
而当mid要替换掉high时,则有点变化了,因为high替换掉mid时,说明mid和high是一种情况,这道题中就是mid也是没有结点的,所以要mid-1;肯定要向前推进一个。
class Solution {
public:
int countNodes(TreeNode* root) {
if(!root)
return 0;
TreeNode *pmove=root;
int level=0;
while(pmove->left)
{level++;pmove=pmove->left;}
//root为第0层,所以level是这么算的
int low=1<<level,high=(1<<(level+1))-1;
//low=2^level;high=2^(level+1)-1;这个不难推导,等比数列求和嘛。需要注意的是位运算符的优先级比较高,所以要括号括起来
while(low<high)//中止条件
{
int mid=(high-low+1)/2+low;
//exist的返回值为mid结点是否存在,存在为1,不存在为0
if(exsit(root,level,mid))
low=mid;//mid结点存在,就向后推进
else
high=mid-1;//不存在,向前推进
}
return low;
}
那么位运算我们要比较什么呢?其实是比较mid的二进制,举个例子如果mid=11;二进制为1011;
此时level=3;那我们从跟结点到最后一层要经过几次呢?答案是三次,而我们知道最后一层是从8-15;8的二进制为1000,15的二进制为1111;所以这里我们应该就明白了,实际上是要看mid二进制的后level(3)位。
那么该如何看呢?0是左孩子,1是右孩子。不难理解,不管是从8-15的二进制比较,还是从8-15的二进制变化来看,都可以。那么关键是如何得到level位呢?我们计算也是把8的二进制最左边视为第0位;
有个好办法就是我们找一个2^level-1次方的数,做位运算,还是举例子吧;比方说11,依次和4,2,1做位与运算,那么就是1011和0100,1011和0010,1011和0001做位运算,结果刚好是第level(1-3)位的结果。这个需要去理解位运算的性质。而从4-2-1,0(0作为中止条件)也很好办,每次右移一位就可以了。至此就结束了
bool exsit(TreeNode *root,int level,int k)
{
//
int comp=1<<(level-1);
for(;comp;comp=comp>>1)
if(comp&k)
root=root->right;
else
root=root->left;
return root?true:false;
}
};
以上代码可以运用三元运算符简化代码行数。
class Solution {
public:
int countNodes(TreeNode* root) {
if(!root)
return 0;
TreeNode *pmove=root;
int level=0;
while(pmove->left)
{level++;pmove=pmove->left;}
int low=1<<level,high=(1<<(level+1))-1;
while(low<high)
{
int mid=(high-low+1)/2+low;
exsit(root,level,mid)?low=mid:high=mid-1;
}
return low;
}
bool exsit(TreeNode *root,int level,int k)
{
for(int comp=1<<(level-1);comp;comp=comp>>1)
root=comp&k?root->right:root->left;
return root?true:false;
}
};
下面另一种解法,完全二叉树的话,必有一颗子树是满二叉树。所以根据这个性质可以去做递归
int countNodes(TreeNode* root) {
if(!root)
return 0;//中止条件空结点自然为0
TreeNode *pmove=root->left;
//分别查找左右子树的最左边高度
int cleft=0,cright=0;
while(pmove)
{cleft++;pmove=pmove->left;}
pmove=root->right;
while(pmove)
{cright++;pmove=pmove->left;}
//如果二者相同,说明,左子树是一个满二叉树,反之,右子树是一个满二叉树
if(cleft>cright)
//若右子树是满二叉树,则返回值为右子树结点加根节点的数目和(为2的n次方)加上递归左子树,反之同理
return (1<<cright)+countNodes(root->left);
else
return (1<<cleft)+countNodes(root->right);
}
};
当然上述代码可以再优化一次,这样的话除了第一次,左右子树都只会查询一次,虽然时间复杂度的还是O(logN*logN)级别,但是少了一半
class Solution {
public:
int count(TreeNode *root,int cleft)
{
if(!root)
return 0;
TreeNode *pmove;
if(!cleft)
{
pmove=root->left;
while(pmove)
{cleft++;pmove=pmove->left;}
}
pmove=root->right;
int cright=0;
while(pmove)
{cright++;pmove=pmove->left;}
if(cleft>cright)
return (1<<cright)+count(root->left,cleft-1);
else
return (1<<cleft)+count(root->right,cleft-1);
}
int countNodes(TreeNode* root) {
return count(root,0);
}
};