Leetcode第222题 完全二叉树的节点个数 C++解法

只说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);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值