完全二叉树节点个数
最简单的解法就是遍历这棵树,时间复杂度是O(N),如果只是这一种解法,就不会有本文了。本文中给出两种解法。
完全二叉树
第一次看到完全二叉树的定义比较懵,搜索到的定义如下:
设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层所有的结点都连续集中在最左边,这就是完全二叉树。
谁到不想看这个定义,比较简单的理解就是和满二叉树相比较,看按层次遍历的标号是否一致。具体方案不再阐述,网上都有。
实现
- 遍历
- 借助完全二叉树的特点
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
遍历
看一下递归的公式:
- 空树:返回0
- 非空树:递归的统计左右子树的节点个数,返回左右子树节点个数加1的和
func countNodes(root *TreeNode) int {
if root == nil {
return 0
}
sumL := countNodes(root.Left)
sumR := countNodes(root.Right)
return sumL+sumR+1
}
借助完全二叉树的特点
这个特点是什么呢?完全二叉树=满二叉树+完全二叉树。完全二叉树里藏着一颗满二叉树和另外一颗完全二叉树,最惊喜的是从公式里看出子问题和原问题基本一致,可以使用递归啊。
完全二叉树的层数
这种算法的实现需要知道完全二叉树的层数,难道又需要遍历统计吗,当然不需要了,还是借助完全二叉树的特点,可以根据完全二叉树的最左节点和一个计数变量搞定,因为这个最左节点肯定是位于最后一层。
func height(root *TreeNode) int {
h := 0
for root != nil {
h++
root = root.Left
}
return h
}
原理
- h = height(root), hright = height(root.Right)
- 左子树和右子树其中有一颗是完全二叉树,另一颗是满二叉树
- 区别左右子树的种类根据h-hright
- h-hright=1:
- 左子树:满二叉树
- 右子树:完全二叉树
- h-hright=2:
- 左子树:完全二叉树
- 右子树:满二叉树
- h-hright=1:
- 节点个数=1+左子树节点个数+右子树节点个数 = (1 << hright)+递归另一颗完全二叉树(满二叉树 = 1 << hright)
第一种情况
第二种情况
实现
func countNodes(root *TreeNode) int {
if root == nil {
return 0
}
return nodeNums(root)
}
func nodeNums(root *TreeNode) int {
h := height(root)
hR := height(root.Right)
//两种情况满二叉树层数都是hR
//不同在于递归左子树还是右子树
nums := 1 << uint(hR)
if hR+1 == h {
return nums+countNodes(root.Right)
}
return nums+countNodes(root.Left)
}
func height(root *TreeNode) int {
h := 0
for root != nil {
h++
root = root.Left
}
return h
}
时间复杂度
对于每一个节点最多需要访问深度个数的节点,所以最终时间复杂度是O(lg2*lg2)。
关于作者
大四学生一枚,分析数据结构,面试题,golang,C语言等知识。QQ交流群:521625004。微信公众号:后台技术栈。