先序
递归一下就出来了
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
if not root.left and not root.right:
return 1
return self.countNodes(root.left) + self.countNodes(root.right) + 1
BFS
input是一个完全二叉树,可以考虑用完全二叉树的性质来做
显然如果某个结点没有右孩子而有左孩子,或者左右孩子都没有,那么这个结点肯定属于这棵树的第h - 1 层,假设这棵树高为h,则共有结点
2
h
−
1
2^h-1
2h−1个,我们可以统计出前h-1层二叉树的总结点数是
2
h
−
1
−
1
2^{h-1}-1
2h−1−1个
然后统计下一层的叶子节点数即可
我们用BFS来遍历,遍历过程中不断统计下一层的结点数,只要我们碰到第一个没有右孩子的结点,我们就可以直接可以得到结果
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
que = [root]
h = 1
cnt = 0
while que:
cnt = 0
size = len(que)
for i in range(size):
front = que.pop(0)
if front.left:
que.append(front.left)
cnt += 1
if not front.right:
return 2**(h)-1+cnt
if front.right:
que.append(front.right)
cnt += 1
h+=1
但是这还是太慢了,因为比起递归,仍然遍历前h-1层的大部分节点,并没有得到什么改进
利用性质的递归
考虑到一颗完全二叉树叶子结点只会出现在最后两层,那么一共两种形态
假设树高为h,左右子树的树高分别为left.h和right.h
-
left.h>right.h
如图(图片来自https://leetcode-cn.com/problems/count-complete-tree-nodes/solution/c-san-chong-fang-fa-jie-jue-wan-quan-er-cha-shu-de/)
此时右子树是一颗满二叉树,右子树的结点数为 2 r i g h t . h − 1 2^{right.h}-1 2right.h−1 -
left.h == right.h ,此时左子树除了最后一层是一颗满二叉树
此时左子树是一颗满二叉树,左子树的结点数为 2 l e f t . h − 1 2^{left.h}-1 2left.h−1
那么由此我们可以写出递归代码
def func(root):
if not root:
return 0
统计左右子树树高
if lh==rh:
# 此时树能够拆分成一颗满二叉树加上一个根节点和一颗完全二叉树,我们处理剩下的完全二叉树即可
return 2^{lh} + func(root.right)
else:
return 2^{rh} + func(root.left)
那么我们还需要求左右子树的高度,在这题中,一个巧妙的做法是,我们沿着左右子树的左孩子一直走就能得到它们各自的深度
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
lh = self.getHigh(root.left)
rh = self.getHigh(root.right)
if lh == rh:
return 2**lh + self.countNodes(root.right)
else:
return 2**rh + self.countNodes(root.left)
def getHigh(self,root):
if not root:
return 0
return self.getHigh(root.left) +1
这个版本的算法就快很多了,时间复杂度是 O ( l o g n ∗ l o g n ) O(logn*logn) O(logn∗logn) 可以用主定理证明
T(n) = T(n/2) + lgn
其中lgn的操作是求树高,T(n/2)是求子树的解
对应主定理第二种情况,算导第三版的P54
注意到上述解法还有优化的余地,我们可以开O(n)的空间来保存树高信息,避免迭代的过程中重复计算,或者我们将上面的递归改成迭代的版本
二分+bit
明天再写