LeetCode 110.平衡二叉树
前序遍历实现
思路:
- 以统一标尺去求每个处理节点的高度,并比较左右子树的高度差。
- 进行前序遍历操作,从上到下。整体平衡不代表局部平衡,因此如果左/右子树出现了不平衡现象,也要及时返回不平衡的信息。
如下图,虽整体是平衡的,但在左子节点2和右子节点2下的左子树和右子树是不平衡的。如果此时没及时返回不平衡的信息的话,则会继续向下遍历,那到节点3的时候,该子树已经平衡了,此时就掩盖了左子树不平衡的信息。
手撕Code
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def isBalanced(self, root):
"""
:type root: Optional[TreeNode]
:rtype: bool
"""
### 平衡二叉树 是指该树所有节点的左右子树的高度相差不超过 1。
### 判断处理节点的左子树高度和右子树高度是否大于1
if not root:
return True
#### 单次递归逻辑———— 前序遍历,中左右
left_tree_height = self.get_Height(root.left)
right_tree_height = self.get_Height(root.right)
left_tree = self.isBalanced(root.left)
if not left_tree: ### 左子树存在不平衡
return False
right_tree = self.isBalanced(root.right)
if not right_tree: ### 右子树存在不平衡
return False
if abs(left_tree_height - right_tree_height) > 1:
return False
else:
return True
def get_Height(self, root):
"""
:获得处理节点的左右子树的高度
:type root: Node
:rtype: bool
"""
if not root:
return 0 ## 确定终止条件
left_tree_height = self.get_Height(root.left) ### 左子树高度
right_tree_height = self.get_Height(root.right) ### 右子树高度
return 1 + max(left_tree_height, right_tree_height) ### 返回处理节点的高度,统一标尺
代码优化:
上述代码的实现步骤为两步:1. 后序遍历求整棵树各节点的高度 2. 前序遍历整颗树判断是否平衡
代码优化的实现步骤为一步,将判断整棵树是否平衡整合到求整棵树各节点的高度中。具体地,在后续遍历求整棵树各节点的高度中,如果已经知道某个节点下已经不平衡的情况下,则直接return一个值用以标识不平衡的信息并向上传递这个不平衡信息,后续在调用该函数的时候只需要判断是否存在这个不平衡信息,通过是否存在不平衡信息来判断树是否平衡。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def isBalanced(self, root):
"""
:type root: Optional[TreeNode]
:rtype: bool
"""
### 平衡二叉树 是指该树所有节点的左右子树的高度相差不超过 1。
### 判断处理节点的左子树高度和右子树高度是否大于1
result = self.get_Height(root)
if result == -1:
return False
else:
return True
def get_Height(self, root):
"""
:获得处理节点的左右子树的高度
:type root: Node
:rtype: bool
"""
if not root:
return 0 ## 确定终止条件
left_tree_height = self.get_Height(root.left) ### 左子树高度
if left_tree_height == -1 :
return -1
right_tree_height = self.get_Height(root.right) ### 右子树高度
if right_tree_height == -1:
return -1
if abs(left_tree_height - right_tree_height) > 1: ### 单次递归逻辑,后续遍历实现,从下到上,如果下面已经有不平衡子树了话,就return -1,告诉父节点下面不平衡了
return -1
else:
return 1 + max(left_tree_height, right_tree_height) ### 返回处理节点的高度,统一标尺
LeetCode257. 二叉树的所有路径
涉及到回溯算法,需要理解回溯的思路。
思路:
- 由于是找根节点到叶子节点的所有路径,从上到下,因此适合于用前序遍历来对树进行遍历操作。
- 在这个递归的过程中,我们需要从上到下遍历到叶子节点,并通过一个路径变量,来记录从根节点遍历到当前叶子节点的路径,当到了叶子节点之后,再通过另一个结果变量来存储当前的路径变量。随后需要进行回溯,将当前处理节点向上不断进行回溯,直到整棵树遍历完毕。
如下面这颗树,假设处理当前节点的指针为cur,那遍历的过程如表所示
cur:1 | path= [1] | ||
cur:2 | path = [1,2] | ||
cur:3 | path = [1,2,3] | ||
cur:7 | path = [1,2,3,7] | ||
result = [1,2,3,7] | 由于节点7无左右子节点,因此将path存储到result中 | ||
path = [1,2,3] | cur为7执行完后,不再进行递归操作,进行回溯,因此path.pop() | ||
cur:8 | path = [1,2,3,8] | result = [1,2,3,8] | 由于节点8无左右子节点,因此将path存储到result中 |
path = [1,2,3] | cur为8执行完后,不再进行递归操作,进行回溯,因此path.pop() | ||
path = [1,2] | cur为3的所有递归操作执行完毕后,进行回溯,path.pop() | ||
path = [1] | cur为2的所有递归操作执行完毕后,进行回溯,path.pop() | ||
cur:4 | path = [1,4] | ||
... | ... | ... | ... |
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def binaryTreePaths(self, root):
"""
:type root: Optional[TreeNode]
:rtype: List[str]
"""
path = []
result = []
if not root:
return result
self.traversal(root, path, result)
return result
def traversal(self, cur, path, result): #### 要的是根节点到叶子节点的路径,因此适合采用前序遍历。
"""
:cur 表示当前处理的节点
: path 用于存储已遍历过的节点
: result 用于从下到上,来存储从叶子节点开始逐渐pop的节点
"""
path.append(cur.val) ### 存储当前节点值
if not cur.left and not cur.right: ### 到达叶子节点的情况
sPath = '->'.join(map(str, path)) ### map(str, path) 是将数组里的每一个元素取出,并转为str类型。join操作将数组转为字符串,'->'.join表示在元素之间添加 -> 符号
result.append(sPath) ### 将当前遍历结果存储到result中
return ### 确定终止条件
#### 前序遍历的思路,先遍历左子树再遍历右子树
if cur.left: ### 有左叶子节点的情况
self.traversal(cur.left, path, result)
path.pop() ### 回溯
if cur.right: ### 有右叶子节点的情况
self.traversal(cur.right, path, result)
path.pop() ### 回溯
#### 上述为什么要回溯,是需要重点理解的地方
LeetCode 404.左叶子之和
思路:
- 类似于判断二叉树是否可对称。需要通过父节点来判断是否左叶子。左叶子之和 = 左子树外侧叶子节点 + 右子树内侧叶子节点
- 这道题有坑,只需要求左叶子节点的值,因此在求左边的过程中,要判断当前节点是否是叶子节点。
手撕Code:
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def sumOfLeftLeaves(self, root):
"""
:type root: Optional[TreeNode]
:rtype: int
"""
### 类似于求二叉树是否可对称.左叶子之和 = 左子树的外侧叶子节点 + 右子树的内测叶子节点
result = self.get_leftSum(root)
return result
def get_leftSum(self, root):
if not root: ##终止条件
return 0
### 采用后序遍历方法, 左右中,将左子树和右子树的左节点之和返回
### 单次递归逻辑 ———— 关键是对左节点的判断,针对右节点只需要让其进行递归进行判断即可
if root.left: ### 存在左子节点
if not root.left.left and not root.left.right: ### 不存在子节点的话,当前就是左叶子节点
left_tree_sum = root.left.val
else: ### 当前root.left节点非左叶子节点
left_tree_sum = self.get_leftSum(root.left)
else:
left_tree_sum = 0
if root.right: ### 存在右子节点
right_tree_sum = self.get_leftSum(root.right)
else:
right_tree_sum = 0
left_Sum = left_tree_sum + right_tree_sum
return left_Sum
精简Code:
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def sumOfLeftLeaves(self, root):
"""
:type root: Optional[TreeNode]
:rtype: int
"""
### 类似于求二叉树是否可对称.左叶子之和 = 左子树的外侧叶子节点 + 右子树的内测叶子节点
result = self.get_leftSum(root)
return result
def get_leftSum(self, root):
if not root: ##终止条件
return 0
if not root.left and not root.right: ### 父节点保存左叶子节点的值,这里是叶子节点,因此叶子节点返回的是0
return 0
### 采用后序遍历方法, 左右中,将左子树和右子树的左节点之和返回
### 左遍历
left_tree_sum = self.get_leftSum(root.left)
if root.left and not root.left.left and not root.left.right: ## 左遍历的终止条件:是左叶子节点
left_tree_sum = root.left.val
### 右遍历
right_tree_sum = self.get_leftSum(root.right)
left_Sum = left_tree_sum + right_tree_sum
return left_Sum
LeetCode 222.完全二叉树的节点个数
普通二叉树求法:
Version 1: 层序遍历 ———— 迭代法实现。时间复杂度为O(n)
Version 2: 后序遍历 ———— 单层递归逻辑,当前处理节点的节点数目 = 左子树节点数目 + 右子树节点数目 + 1 (1表示的是处理节点本身)
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def countNodes(self, root):
"""
:type root: Optional[TreeNode]
:rtype: int
"""
### Version 2: 后序遍历实现
if not root: ## 处理节点为None
return 0
#### 单次递归逻辑 ———— 计算左子节点数 + 计算右子节点数 + 1(当前处理节点数)
left_tree_sum = self.countNodes(root.left)
right_tree_sum = self.countNodes(root.right)
sum_nodes = left_tree_sum + right_tree_sum + 1 ###容易漏了+1,漏了+1的话以为着只处理了底层节点,并将底层节点数目向上传递
return sum_nodes
注意:
- return 的值中容易忘了+1,因为是后续遍历,当前处理节点本身存储的是其左右子树节点总数还有其本身。
完全二叉树求法:
注意完全二叉树的概念:如果底层有节点,则一定是从左到右连续分布的。
Version 3: 只求最后一层的节点数目,其余层的节点数目可以通过完全二叉树的特性求得。
思路:
- 一颗完全二叉树可以拆分为若干个满二叉树。
如图所示:
首先满二叉树的定义是一棵树的节点一定是填满的,因此其节点数目一定符合 (2 ^ 树的深度 - 1)。
- 对于完全二叉树,其叶子节点一定是从左往右连续分布,因此如果我们通过判断某一颗子树的最左侧深度和最右侧深度,如果深度相同,那么这颗这树一定是满二叉树,如图中1所示。这样的话,我们可以直接通过公式计算出该子树的节点数目,而不用去遍历该子树的所有节点,只需要遍历其最左侧和最右侧。如果深度相同,则可以继续分别往该处理节点的左子节点和右子节点进行递归,一定会存在满二叉树的情况(最差也就是叶子节点)。
- 对于图中的2,左子树并不是一颗满二叉树,但叶子节点本身也是一颗满二叉树,因此针对叶子节点数目也可以基于满二叉树的逻辑进行实现。
- 采用后续遍历,将处理节点的左右子树节点数目相加后再加1,即可得到当前处理节点这颗这树的节点数目。
Code:
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def countNodes(self, root):
"""
:type root: Optional[TreeNode]
:rtype: int
"""
if not root:
return 0
left_nums = self.get_Nums(root.left)
right_nums = self.get_Nums(root.right)
sum_nums = left_nums + right_nums
return sum_nums + 1
def get_Nums(self, root):
if not root: ## 终止条件
return 0
left_depth, right_depth = 1,1
left = root.left
right = root.right
while left:
left_depth += 1
left = left.left
while right:
right_depth += 1
right = right.right
if left_depth == right_depth:
nums = 2 ** left_depth - 1
else:
nums = self.get_Nums(root.left) + self.get_Nums(root.right) + 1
return nums
不要漏了加上处理节点的数目 (return 记得 +1)。