[LeetCode] 222. Count Complete Tree Nodes

Given a complete binary tree, count the number of nodes.

Note:

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.

Example:

Input: 
    1
   / \
  2   3
 / \  /
4  5 6

Output: 6


这一题出得来,就是不可能让你遍历完整棵树的,否则太没有水平了。时间也过不去。

这题的做法本质上有点像二分,因为是完全二叉树,也就是说除了最后一层以外其余子节点都是满的。而且最后一层有一个分界点,从bfs的角度来说分界点的左边全是满的右边全是没有的。很自然的想法就是要找到那个分界点在哪,然后你再找找这棵树有几层,最底层的节点数是2的n - 1次方,然后减去就好。。

这种想法,对也不对。虽然很容易就能知道一棵树有多深。但是找到分界点的方法是很难的。bfs基本可以放弃了,dfs不管是preorder, in-order还是post order也难以避免最坏情况遍历一棵树的可能性(平均情况是遍历半棵树,那和遍历一棵树是同样复杂度的操作)。所以常见的关于树的算法都是不通用的。
换一种思维想,完全二叉树有以下几个特征: 1. 假设一棵树是全满的,它的层数是n,那么它的节点数就是2^n - 1。2. 全满的树,你不停往左走,和不停往右走得到的深度必然是一样的。 3. 在完全二叉树里面,任何一个节点,它的左右子树必然有至少一个是全满的。这题真正的解题关键就在于此3点(主要是第三点比较难意识到)。

所以真正的做法(借鉴于网络)是这样的:
1. 如果当前节点是空指针,返回0。
2. 不停往左走和往右走得到最左深度和最右深度。此时会出现两种情况
3. a. 两个深度相等,这是一棵全满树,直接返回2^n - 1即可
3. b. 两个深度不等(最多差一)。此时同时对左子树和右子树递归从头进行操作。

这种做法之所以效率高于正常的dfs和bfs,是因为如果说一棵树有n层,dfs和bfs的复杂度都是指数级的,但是这个算法的效率再优化一下是o(n^2)级别的。优化其实也很简单,就是在3.b这一步递归到下一个子树的时候,你可以事先知道其中一边的深度。譬如你在母亲节点的时候测量到了最左深度和最右深度,那么你往左子树递归时,这个左子树的最左深度就是上一层的最左深度减一,同理于右子树的最右深度。之所以是O(n^2),主要原因在于上面说到的特征3。

假设我们在走一个高度为n的树的根节点。
1. 左右各走一次深度, 2 * n 操作
2. 左右各进行递归。因为特征3,所以其中一颗子树走完一边就得到了3.a. 另一颗子树可能是3.a,也可能是3.b, 2 * (n - 1) 操作
3. 再得到3.b结果的子树那边继续走,根据特征3,和2的结果一样,一边子树3.a,另一边子树3.a或3.b。 2 * (n - 2)操作。
... 以此类推直到走到底或者出现两边都是3.a的情况。

所以可以看出来,最坏的情况的复杂度就是 2 * ( n + n - 1 + n - 2 + ... 1) 也就是O(n ^ 2) 级别。给出代码如下

    static final int DIR_LEFT  = -1;
    static final int NO_DIR    = 0;
    static final int DIR_RIGHT = 1;

    public int countNodes(TreeNode root) {
        return _countNodeHelper(root, 0, NO_DIR);
    }
    
    private int _countNodeHelper(TreeNode root, int parentDepth, int dir) {
        if (root == null) return 0;
        int leftDepth = dir == DIR_LEFT ? parentDepth - 1 : _countDepth(root, DIR_LEFT);
        int rightDepth = dir == DIR_RIGHT ? parentDepth - 1 : _countDepth(root, DIR_RIGHT);
        
        if (leftDepth == rightDepth) {
            return (2 << (leftDepth - 1)) - 1;
        }
        
        return _countNodeHelper(root.left, leftDepth, DIR_LEFT) + _countNodeHelper(root.right, rightDepth, DIR_RIGHT) + 1;
    }
    
    private int _countDepth(TreeNode root, int dir) {
        int depth = 0;
        while (root != null) {
            depth++;
            root = dir == DIR_LEFT ? root.left : root.right;
        }
        
        return depth;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值