【刷题笔记】LeetCode 222. Count Complete Tree Nodes
题意
给一棵 complete binary tree,数数看一共有多少个结点。做题链接
直观做法:递归
var countNodes = function(root) {
if(root===null) return 0;
return 1+countNodes(root.left)+countNodes(root.right);
};
老实说,一道难度为 medium 的题目,这么几秒钟的时间就做出来,我心中有一种不真实感。
所以,我看了一下 discuss 区其他人的解法,看是不是我自己想的不够深入。
结果发现,我这个做法没错,就是时间复杂度不够优化,如果这道题要真正成为一道难度为medium的题目,需要限制时间复杂度。
下面是一种据说是 O(log(n)^2) 的解法,很聪明,根据不同树结构选择计算左树结点数或右树节点数,可以省去访问大部分结点的时间。
优化做法:计算树高,观察树结构
var countNodes = function(root) {
let h = height(root);
return h === 0 ? 0 :
height(root.right)=== h-1 ? (1 << (h-1)) + countNodes(root.right) :
(1 << (h-2)) + countNodes(root.left) ;
};
function height(root){
return root === null? 0 : 1+height(root.left);
}
首先,height 函数,没啥好说的,跟普通的计算树高度函数不同点在于,充分利用了 complete binary tree 的特点,更加省事。
其次,要理解根据 root 的高度 和 右子树高度的不同情况的选择。
情况一:
height(root) === 0
这个情况没啥好说的,就是边界情况,啥结点都没有,返回0;
情况二:
height(root.right) === h-1
这个情况就是下面图这种情况,右子树至少最左边有一个结点(这个结点把右子树的高度撑到 h-1):

这种情况下,左子树是完整的,它的节点数是 2^(h-1)-1 ,加上根结点,等于 2^(h-1) ,也就是 1 << (h-1) (图中绿色结点总数)。
情况三:
height(root.right) === h-2
这种情况就是右子树最后一层没有任何结点,左子树至少最后一层最左边有一个结点:

这种情况下,右子树是完整的,它的节点数是 2^(h-2)-1 ,加上根结点,等于 2^(h-2) ,也就是 1<<(h-2) (图中绿色结点总数)。
再次优化:用遍历取代递归
直接遍历所有的节点,可以省下反复计算 height 的不必要的时间。
因为很明显,子树的高度 跟 父树的高度 息息相关。如果通过递归,下一次还得重新计算高度值;而用遍历,直接 h--; 就得到子树高度值。(当然你也可以在递归函数中把高度当成参数传递进去,但是不够简洁,没必要)
var countNodes = function(root) {
let h = height(root), nodes = 0;
while(root!==null){
if(height(root.right)===h-1){
nodes += 1 << (h-1);
root = root.right;
}
else{
nodes += 1 << (h-2);
root = root.left;
}
h--;
}
return nodes;
};
function height(root){
return root===null? 0 : 1+height(root.left);
}
第一种简单方法的优化
最开始我用了一种最简单的方法:
var countNodes = function(root) {
if(root===null) return 0;
return 1+countNodes(root.left)+countNodes(root.right);
};
在 LeetCode 的 discuss 区看到 一种优化方法:
var countNodes = function(root) {
if(root===null) return 0;
let left = root.left, right = root.right, height = 1;
while(right !== null){
left = left.left;
right = right.right;
height++;
}
if(left === null) return (1 << height)-1;
return 1 + countNodes(root.left) + countNodes(root.right);
};
这种方法其实也是利用了 Complete Binary Tree 的特点,如果一棵树是完整的(就是它所有树叶都处于同一层),那么在 右子树 为 null 之前, left = left.left; right = right.right; 最左最右两个分枝同时向下延伸,最后一定同时抵达树叶。
这种方法的优化之处在于,当遇到的子树的所有树叶都位于同一层时,它会直接返回该子树的结点数。像下面这样的树,当遇到结点 3, 5, 8 时,不会再调用函数递归,而是直接计算出整颗子树的结点数 (1 << height) - 1 。

总结
要了解数据结构的特点,并且充分利用这些特点来省事。
PS:上面全部代码为 JavaScript
【刷题笔记】LeetCode 222. Count Complete Tree Nodes的更多相关文章
- [LeetCode] 222. Count Complete Tree Nodes 求完全二叉树的节点个数
Given a complete binary tree, count the number of nodes. Note: Definition of a complete binary tree ...
- Java for LeetCode 222 Count Complete Tree Nodes
Given a complete binary tree, count the number of nodes. Definition of a complete binary tree from W ...
- (medium)LeetCode 222.Count Complete Tree Nodes
Given a complete binary tree, count the number of nodes. Definition of a complete binary tree from W ...
- leetcode 222.Count Complete Tree Nodes
完全二叉树是从左边开始一点点填充节点的,因此需要计算所有的节点的个数. 则分别从左边和右边来进行传递的,当左右是完全二叉树的时候,其节点个数就是pow(2,h)-1. /** * Definition ...
- [leetcode]222. Count Complete Tree Nodes完全二叉树的节点数
/* 满二叉树的特点是2^n-1,对于完全二叉树,一个node如果左右子树深度相同,那么 是一个满二叉树.如果不是,那就把node算上,继续往下看,下边的可能是满二叉树 由于完全二叉树中有一些子满二叉 ...
- 【LeetCode】222. Count Complete Tree Nodes 解题报告(Python)
[LeetCode]222. Count Complete Tree Nodes 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个 ...
- leetcode 958. Check Completeness of a Binary Tree 判断是否是完全二叉树 、222. Count Complete Tree Nodes
完全二叉树的定义:若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树. 解题思路:将树按照层进行遍历,如果 ...
- 【刷题-LeetCode】222. Count Complete Tree Nodes
Count Complete Tree Nodes Given a complete binary tree, count the number of nodes. Note: Definition ...
- LeetCode OJ 222. Count Complete Tree Nodes
Total Accepted: 32628 Total Submissions: 129569 Difficulty: Medium Given a complete binary tree, cou ...
随机推荐
- canvas动画简单操作
canvas动画 小球滚动效果 关键api: window.requestAnimationFrame(draw) 会递归调用draw函数,替代setInterval var x = 20; var ...
- 得到Xml中 元素的值
<?xml version="1.0" encoding="utf-8" ?><Response service="RouteSer ...
- go结构,结构嵌套,接口,指针的测试和结论
package main import ( "fmt" ) //T是M1接受者,不是实现M2接受者 //*T是M1接受者,也是M2的接受者 //所以T对象不可以赋值给接口对象.*T ...
- 企业级任务调度框架Quartz(6) 任务调度器(Scheduler)
前序: 我们已经在前面的内容能里看到了,我们用 Scheduler 来管理我们的 Job:创建并关联触发器以使 Job 能被触发执行:以及如可选择 calendar 为给定的时程安排提供更多 ...
- 浅谈[^>]在正则中的2种用法
/^A/会匹配"An e"中的A,但是不会匹配"ab A"中的A,此时^A的意思是“匹配开头的A” /[^a-z\s]/会匹配"my 3 sister ...
- Javascript解析URL
举个栗子,一个网页的URL为https://i.cnblogs.com/EditPosts.aspx?opt=1,要分离出通信协议.host.port.path.query.hash等值.这时候我们应 ...
- Django 连接MySQL 通过pymysql 库
ython3 如何安装pymysql 库,在此不再做多的讲解,如果有想知道如何安装的朋友,请求参考下面的连接地址: 第一步:应用setting.py 文件设置mysql 数据库连接相关属性. DA ...
- BZOJ 3434 [WC2014]时空穿梭 (莫比乌斯反演)
题面:BZOJ传送门 洛谷传送门 好难啊..反演的终极题目 首先,本题的突破口在于直线的性质.不论是几维的空间,两点一定能确定一条直线 选取两个点作为最左下和最右上的点! 假设现在是二维空间,选取了$ ...
- php中文乱码处理方法
昨天在本地环境创建了一个文件,文件编码是UTF-8格式,打印一个简单的语句竟然出现了中文乱码,折腾了很久,才找到了原因. 乱码问题 昨天写了一个很简单的php输出中文页面,但是出现了乱码问题,第一反应 ...
- Django入门--自定义过滤器与标签
---恢复内容开始--- 为了让Django找到自定义的模板过滤器和模板标签,需要进行文件路径配置,配置方式分为APP目录下配置和项目路径下配置两种方式: 1.在APP目录下配置:针对某个应用特定的自 ...