思路:
用(类似)后序遍历来计算
后序遍历的顺序是:左子树、右子树、根。
在本题同样也是这个顺序:递归左子树的最大高度,递归右子树的最大高度,求根的最大高度。
使用递归法,递归三部曲
1.确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型
def getDepth(self,node):
2.确定终止条件:如果为空节点的话,就返回0,表示高度为0
if not node:
return 0
3.确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度
leftheight = self.getDepth(node.left) #左
rightheight = self.getDepth(node.right) #右
height = 1 + max(leftheight, rightheight) #中
return height
代码:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
return self.getDepth(root) # 计算以root为根的二叉树的最大深度
def getDepth(self,node):
if not node: # 如果node为空,则返回深度0,因为空节点没有子树,所以深度为0
return 0
leftheight = self.getDepth(node.left) # 递归地调用getDepth方法,计算当前节点左子树的最大深度
rightheight = self.getDepth(node.right) # 递归地调用getDepth方法,计算当前节点右子树的最大深度
height = 1 + max(leftheight, rightheight) # 计算当前节点(即node)的深度。它取左子树和右子树中较大的深度,然后加1(因为当前节点本身也算一层)
return height # 返回计算得到的当前节点的深度
时间复杂度:O(n),其中 n为二叉树节点的个数。每个节点在递归中只被遍历一次。
空间复杂度:O(height),其中 height表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。在最坏的情况下,当二叉树退化为链表时,递归调用的深度将达到n(节点数量)。因此,空间复杂度在最坏情况下是O(n)。
思路:
直觉上好像和求最大深度差不多,其实还是差不少的。
那么使用后序遍历(递归法),其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。
本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图:
这就重新审题了,题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点。什么是叶子节点,左右孩子都为空的节点才是叶子节点!
递归三部曲:
1.确定递归函数的参数和返回值:参数为要传入的二叉树根节点,返回的是int类型的深度
def getDepth(self, node):
2.确定终止条件:终止条件也是遇到空节点返回0,表示当前节点的高度为0。
if node is None:
return 0
3.确定单层递归的逻辑:如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值再+ 1 。
遍历的顺序为后序(左右中),可以看出:求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
leftDepth = self.getDepth(node.left) # 左
rightDepth = self.getDepth(node.right) # 右
# 当一个左子树为空,右不为空,这时并不是最低点
if node.left is None and node.right is not None:
height=1 + rightDepth
return height
# 当一个右子树为空,左不为空,这时并不是最低点
if node.left is not None and node.right is None:
height=1 + leftDepth
return height
# 当左子树和右子树不为空 或 左子树和右子树都为空
height = 1 + min(leftDepth, rightDepth)
return height
代码:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
return self.getDepth(root)
def getDepth(self, node):
if node is None:
return 0
leftDepth = self.getDepth(node.left) # 左
rightDepth = self.getDepth(node.right) # 右
# 当一个左子树为空,右不为空,这时并不是最低点
if node.left is None and node.right is not None:
height=1 + rightDepth # 当前节点的最小深度为右子树的最小深度加一
return height
# 当一个右子树为空,左不为空,这时并不是最低点
if node.left is not None and node.right is None:
height=1 + leftDepth # 当前节点的最小深度为左子树的最小深度加一
return height
# 当左子树和右子树不为空 或 左子树和右子树都为空
height = 1 + min(leftDepth, rightDepth) # 计算当前节点(即node)的最小深度
return height
时间复杂度:O(N),其中 N是树的节点数。对每个节点访问一次。
空间复杂度:O(H),其中 H是树的高度。空间复杂度主要取决于递归时栈空间的开销,最坏情况下,树呈现链状,空间复杂度为 O(N)。平均情况下树的高度与节点数的对数正相关,空间复杂度为 O(logN)
思路:
1.按照普通二叉树的逻辑来求
这道题目的递归法和求二叉树的深度写法类似
递归遍历的顺序依然是后序(左右中)
递归三部曲:
1.确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。
2.确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
3.确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
2.利用完全二叉树特性的解法
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
这里关键在于如何去判断一个树是不是满二叉树呢?
在完全二叉树中,如果向左遍历的深度等于向右遍历的深度(左子树的深度等于右子树的深度),那说明就是满二叉树。如图:
在完全二叉树中,如果向左遍历的深度不等于向右遍历的深度,则说明不是满二叉树,如图:
代码:
按照普通二叉树的逻辑来求
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
return self.getNodesNum(root)
def getNodesNum(self,node):
if not node:
return 0
leftNum = self.getNodesNum(node.left) # 当前节点左子树的节点数
rightNum = self.getNodesNum(node.right) # 当前节点右子树的节点数
treeNum = leftNum + rightNum + 1 # 当前节点为根节点的树的节点数
return treeNum
- 时间复杂度:O(n)
- 空间复杂度:O(log n)
利用完全二叉树特性
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root: # 如果根节点为None(即树为空),则直接返回0,因为空树没有节点
return 0
# 初始化左子树和右子树的指针,以及它们的深度(初始化为0)
left = root.left
right = root.right
leftDepth = 0
rightDepth = 0
while left: # 通过不断地向左移动直到没有左子节点,计算左子树的深度
left = left.left
leftDepth += 1
while right: # 通过不断地向右移动直到没有右子节点,计算右子树的深度
right = right.right
rightDepth += 1
if leftDepth == rightDepth: # 如果左子树的深度等于右子树的深度,说明当前树是一个完全二叉树。在这种情况下,我们可以使用数学公式2^(深度+1) - 1来快速计算节点总数
return 2 ** (leftDepth+1) - 1
else: # 如果当前节点为根节点的树不是完全二叉树,则递归地计算左子树和右子树的节点数并相加,然后加上当前根节点(即+1),得到整棵树的节点总数
leftTreeNum=self.countNodes(root.left)
rightTreeNum=self.countNodes(root.right)
treeNum=leftTreeNum + rightTreeNum + 1
return treeNum
- 时间复杂度:O(log n × log n),每次计算满二叉树的时候,计算的其实就是当前树高,即 O(logn),每次递归调用的都是下一层的子树,总共调用了“树的高度”次,即 O(logn),所以时间复杂度为 O(logn) * O(logn)
- 空间复杂度:O(log n),使用了递归,额外调用了栈空间,所以空间复杂度为 O(logn)