今天我们来集中解决LeetCode上四道经典的二叉树题目,它们涵盖了平衡性判断、特殊节点求和、路径遍历和节点统计等不同考察方向。这些题目在面试中出现频率很高,掌握它们能帮你快速建立解决二叉树问题的思维框架。
我在刷题过程中发现,很多二叉树题目看似独立,实则存在共通的解题模式。通过这四道题的对比练习,你将学会如何根据问题特点选择递归或迭代解法,并理解二叉树遍历的各种变体应用场景。下面我们按题目编号顺序逐一拆解。
平衡二叉树的定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。注意这个定义是递归的,意味着所有子树都必须满足这个条件。
常见误区:只检查根节点的左右子树高度差,而忽略了对所有子树的检查。
标准的自顶向下递归解法:
python复制def isBalanced(root):
def height(node):
if not node:
return 0
return max(height(node.left), height(node.right)) + 1
if not root:
return True
return abs(height(root.left) - height(root.right)) <= 1 and \
isBalanced(root.left) and isBalanced(root.right)
这个解法虽然直观,但存在重复计算问题。对于每个节点,我们都要计算其子树的高度,导致时间复杂度达到O(n^2)。
更高效的解法是在计算高度的同时检查平衡性:
python复制def isBalanced(root):
def check(node):
if not node:
return 0, True
left_height, left_balanced = check(node.left)
right_height, right_balanced = check(node.right)
current_height = max(left_height, right_height) + 1
is_balanced = left_balanced and right_balanced and \
abs(left_height - right_height) <= 1
return current_height, is_balanced
_, result = check(root)
return result
这种自底向上的方法每个节点只访问一次,时间复杂度优化到O(n)。关键点在于让辅助函数返回两个信息:当前子树的高度和是否平衡。
左叶子是指:是父节点的左子节点,且自身没有子节点。这个定义容易误解,需要特别注意:
python复制def sumOfLeftLeaves(root):
if not root:
return 0
def isLeaf(node):
return not node.left and not node.right
if root.left and isLeaf(root.left):
return root.left.val + sumOfLeftLeaves(root.right)
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right)
使用栈的深度优先搜索实现:
python复制def sumOfLeftLeaves(root):
if not root:
return 0
stack = [(root, False)] # (node, is_left)
total = 0
while stack:
node, is_left = stack.pop()
if not node.left and not node.right and is_left:
total += node.val
if node.right:
stack.append((node.right, False))
if node.left:
stack.append((node.left, True))
return total
迭代法的关键在于标记节点是否是左子节点。使用元组(node, is_left)来保存这个状态信息。
需要返回从根节点到所有叶子节点的路径。这属于典型的深度优先搜索(DFS)应用场景,可以使用回溯法解决。
python复制def binaryTreePaths(root):
def construct_paths(node, path):
if node:
path += str(node.val)
if not node.left and not node.right:
paths.append(path)
else:
path += "->"
construct_paths(node.left, path)
construct_paths(node.right, path)
paths = []
construct_paths(root, "")
return paths
使用栈模拟递归过程:
python复制def binaryTreePaths(root):
if not root:
return []
paths = []
stack = [(root, str(root.val))]
while stack:
node, path = stack.pop()
if not node.left and not node.right:
paths.append(path)
if node.right:
stack.append((node.right, path + "->" + str(node.right.val)))
if node.left:
stack.append((node.left, path + "->" + str(node.left.val)))
return paths
路径拼接技巧:在迭代法中,我们提前构建好到当前节点的路径字符串,这样遇到叶子节点时可以直接加入结果集,避免了字符串的重复处理。
完全二叉树定义:除了最后一层,其他层的节点都达到最大数量,且最后一层的节点都集中在左侧。
直接遍历所有节点的方法时间复杂度为O(n),但我们可以利用完全二叉树的性质进行优化。
python复制def countNodes(root):
if not root:
return 0
def get_depth(node):
depth = 0
while node:
depth += 1
node = node.left
return depth
left_depth = get_depth(root.left)
right_depth = get_depth(root.right)
if left_depth == right_depth:
return (1 << left_depth) + countNodes(root.right)
else:
return (1 << right_depth) + countNodes(root.left)
这个解法的时间复杂度为O(log n * log n),因为:
更极致的优化是使用二分查找定位最后一层的节点:
python复制def countNodes(root):
if not root:
return 0
d = get_depth(root)
if d == 0:
return 1
left, right = 1, 2**d - 1
while left <= right:
pivot = left + (right - left) // 2
if exists(pivot, d, root):
left = pivot + 1
else:
right = pivot - 1
return (2**d - 1) + left
def get_depth(node):
depth = 0
while node.left:
depth += 1
node = node.left
return depth
def exists(idx, d, node):
left, right = 0, 2**d - 1
for _ in range(d):
pivot = left + (right - left) // 2
if idx <= pivot:
node = node.left
right = pivot
else:
node = node.right
left = pivot + 1
return node is not None
这个算法将时间复杂度优化到O(log² n),是最优解法。它利用了完全二叉树可以按层编号的特性,通过二分查找确定最后一层的节点数量。
通过这四道题目,我们可以总结出二叉树问题的通用解题模式:
递归三要素:
遍历方式选择:
优化方向:
常见陷阱:
在实际面试中,建议先明确问题定义,举例验证理解是否正确,然后从暴力解法开始,逐步优化。二叉树问题往往有多种解法,选择最适合当前问题特性的方法最为关键。