110. 平衡二叉树
难度:☆4
a. 递归法:后序遍历
二叉树求高度用后序遍历,求深度用前序遍历。求高度需要左右子树的高度,从下往上累计,所以用后序遍历。本题求高度,用递归实现 getHeight()
函数。
判断二叉树是否平衡,需要求左右子树的高度差与 1 比较,用内置函数 abs()
。通过 -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 isBalanced(self, root: Optional[TreeNode]) -> bool:
return False if self.getHeight(root) == -1 else True
def getHeight(self, node: Optional[TreeNode]) -> int:
if not node:
return 0
leftHeight = self.getHeight(node.left) # 左
if leftHeight == -1:
return -1
rightHeight = self.getHeight(node.right) # 右
if rightHeight == -1:
return -1
if abs(rightHeight - leftHeight) > 1: # 中(后序遍历)
result = -1 # 不平衡,返回-1
else:
result = 1 + max(leftHeight, rightHeight) # 平衡,取左右子树高度的较大值
return result
b. 迭代法
迭代法比较复杂,贴一个代码以供后续研究。
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
height_map = {}
stack = [root]
while stack:
node = stack.pop()
if node:
stack.append(node)
stack.append(None)
if node.left: stack.append(node.left)
if node.right: stack.append(node.right)
else:
real_node = stack.pop()
left, right = height_map.get(real_node.left, 0), height_map.get(real_node.right, 0)
if abs(left - right) > 1:
return False
height_map[real_node] = 1 + max(left, right)
return True
257. 二叉树的所有路径
难度:☆5
需要维护一个路径(字符串)和一个结果(数组),如果当前节点为叶子节点,将路径添加到结果中。回溯比较陌生,还需再来仔细研究。
a. 递归法:前序遍历+回溯
较难。递归和回溯是一家,本题需要递归,也需要回溯。回溯操作是删除path的最后两个字符 ->
。回溯和递归是一一对应的,有一个递归,就要有一个回溯,它们相邻。
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
path = ''
result = []
if not root:
return result
self.traversal(root, path, result)
return result
def traversal(self, cur: TreeNode, path: str, result: List[str]) -> None:
path += str(cur.val)
if not cur.left and not cur.right:
result.append(path)
if cur.left:
path += '->'
self.traversal(cur.left, path, result)
path = path[:-2] # 回溯
if cur.right:
path += '->'
self.traversal(cur.right, path, result)
path = path[:-2] # 回溯
b. 递归法:前序遍历+隐藏回溯
修改以上代码的最后几行,得到隐藏回溯的实现。注意 + '->'
作为函数参数出现。
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
path = ''
result = []
if not root:
return result
self.traversal(root, path, result)
return result
def traversal(self, cur: TreeNode, path: str, result: List[str]) -> None:
path += str(cur.val)
if not cur.left and not cur.right:
result.append(path)
if cur.left:
# + '->' 是隐藏回溯
self.traversal(cur.left, path + '->', result)
if cur.right:
# + '->' 是隐藏回溯
self.traversal(cur.right, path + '->', result)
官方题解如下,深度优先搜索-前序遍历,隐藏回溯的代码。注意 + '->'
放在递归调用之前。
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
def constructPaths(node, path):
if node:
path += str(node.val)
if not node.left and not node.right: # 当前节点是叶子节点
paths.append(path) # 把路径加入到答案中
else:
path += '->' # 当前节点不是叶子节点,继续递归遍历
constructPaths(node.left, path)
constructPaths(node.right, path)
paths = []
constructPaths(root, '')
return paths
c. 迭代法:层序遍历
用两个队列实现,一个存放遍历的每个节点,另一个存放待完成拼接的所有路径。
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
paths = []
if not root:
return paths
node_queue = collections.deque([root])
path_queue = collections.deque([str(root.val)])
while node_queue:
node = node_queue.popleft()
path = path_queue.popleft()
if not node.left and not node.right:
paths.append(path)
else:
if node.left:
node_queue.append(node.left)
path_queue.append(path + '->' + str(node.left.val))
if node.right:
node_queue.append(node.right)
path_queue.append(path + '->' + str(node.right.val))
return paths
404. 左叶子之和
难度:☆3
思维难点:本题不能通过当前节点判断是不是左叶子节点,要通过当前节点的父节点来判断其左孩子是不是左叶子。平时我们解二叉树的题目时,已经习惯了通过节点的左右孩子判断本节点的属性,而本题我们要通过节点的父节点判断本节点的属性。
a. 递归法:后序遍历
用递归法来实现后序遍历(左右中),因为要通过递归函数的返回值,来累计左叶子数值之和。
第5、6行:只有当前遍历的节点是父节点,才能判断其左孩子是不是左叶子。 如果当前遍历的节点是叶子节点,其左叶子之和必定是0。
单层递归的逻辑:当遇到左叶子的时候,记录数值,然后通过递归求取左子树的左叶子之和,和右子树的左叶子之和,相加得到整棵树的左叶子之和。
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
if not root.left and not root.right:
return 0
leftSum = self.sumOfLeftLeaves(root.left) # 左
if root.left and not root.left.left and not root.left.right:
leftSum = root.left.val
rightSum = self.sumOfLeftLeaves(root.right) # 右
sumLeaves = leftSum + rightSum # 中
return sumLeaves
精简版看不出是后序遍历了:
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
leftSum = 0
if root.left and not root.left.left and not root.left.right:
leftSum = root.left.val
return leftSum + self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)
b. 迭代法:前序遍历
迭代法,使用前中后序都是可以的。统计左叶子之和,判断条件依旧是看父节点的左孩子是不是左叶子。
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
stack = []
if root:
stack.append(root)
result = 0
while stack:
cur = stack.pop()
if cur.left and not cur.left.left and not cur.left.right: # 中
result += cur.left.val
if cur.left: # 左
stack.append(cur.left)
if cur.right: # 右
stack.append(cur.right)
return result