1. 二叉树算法实战:从基础到进阶的四个经典问题
今天我想和大家分享四个关于二叉树的经典算法问题,这些问题在面试和日常编程中经常遇到。我们将从平衡二叉树的判断开始,逐步深入到完全二叉树的节点计数,每个问题都配有详细的解题思路和代码实现。
作为一名有多年算法教学经验的开发者,我发现很多同学在学习二叉树时容易陷入"知道概念但不会应用"的困境。这四个问题恰好覆盖了二叉树的不同应用场景,通过它们可以建立起对二叉树更深入的理解。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 110. 平衡二叉树的判断与优化
2.1 什么是平衡二叉树?
平衡二叉树(Balanced Binary Tree)是指任意节点的左右子树高度差不超过1的二叉树。这个概念由Adelson-Velsky和Landis提出,因此也被称为AVL树。在实际应用中,平衡二叉树能保证查找、插入、删除等操作的时间复杂度为O(log n)。
判断一棵树是否为平衡二叉树的核心在于计算每个节点的左右子树高度差。如果所有节点都满足高度差≤1的条件,那么这棵树就是平衡的。
2.2 递归解法与时间复杂度分析
最直观的解法是使用递归,自顶向下地检查每个节点:
python复制def isBalanced(root):
if not root:
return True
def height(node):
if not node:
return 0
return max(height(node.left), height(node.right)) + 1
left_height = height(root.left)
right_height = height(root.right)
return abs(left_height - right_height) <= 1 and \
isBalanced(root.left) and \
isBalanced(root.right)
这种解法的时间复杂度是O(n^2),因为对于每个节点,我们都要计算它的子树高度。在最坏情况下(如链表形式的树),会有大量重复计算。
2.3 优化解法:自底向上的递归
我们可以优化为自底向上的方法,在计算高度的同时检查平衡性:
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),因为我们只遍历每个节点一次。在实际面试中,面试官通常会期待你提出这种优化方案。
提示:当面试官问及平衡二叉树时,他们往往期待你讨论AVL树和红黑树的区别。AVL树更严格平衡,适合查找密集型操作;红黑树牺牲了一点平衡性换取更高效的插入/删除操作。
3. 257. 二叉树的所有路径:回溯算法的经典应用
3.1 问题理解与递归思路
这个问题要求我们找出从根节点到所有叶子节点的路径。例如:
code复制 1
/ \
2 3
\
5
输出应为:["1->2->5", "1->3"]
递归是解决这类路径问题的自然选择。我们需要:
- 维护当前路径
- 到达叶子节点时将路径加入结果
- 递归处理左右子树
3.2 递归实现与回溯
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
这里的关键是理解递归调用前后的状态变化。path字符串在每次递归调用时都是独立的,因此不需要显式回溯(不像列表需要pop操作)。
3.3 迭代解法:深度优先搜索的栈实现
递归解法虽然简洁,但面试官可能会要求迭代实现:
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)。在实际应用中,当树很深时,迭代解法可以避免递归栈溢出的风险。
4. 404. 左叶子之和:如何正确识别左叶子
4.1 左叶子的定义与识别
左叶子是指某个节点的左子节点,并且这个左子节点本身是叶子节点(没有左右子节点)。例如:
code复制 3
/ \
9 20
/ \
15 7
这里9是左叶子,15不是(它是右子节点),7也不是(它是右子节点)。总和为9。
4.2 递归解法与父节点信息传递
关键点在于我们需要知道当前节点是否是父节点的左子节点:
python复制def sumOfLeftLeaves(root):
def helper(node, is_left):
if not node:
return 0
if not node.left and not node.right and is_left:
return node.val
return helper(node.left, True) + helper(node.right, False)
return helper(root, False)
这个解法通过is_left参数记录当前节点是否是父节点的左子节点,从而正确识别左叶子。
4.3 迭代解法:广度优先搜索
我们也可以用广度优先搜索(BFS)来实现:
python复制def sumOfLeftLeaves(root):
if not root:
return 0
queue = deque([(root, False)])
total = 0
while queue:
node, is_left = queue.popleft()
if not node.left and not node.right and is_left:
total += node.val
if node.left:
queue.append((node.left, True))
if node.right:
queue.append((node.right, False))
return total
BFS解法在每层遍历时同样记录is_left标志,确保正确识别左叶子。这种解法适合处理较为平衡的树结构。
注意:很多同学容易犯的错误是只检查node.left是否为叶子节点,而忽略了需要确认这个node.left确实是父节点的左子节点。在面试中,一定要明确询问面试官左叶子的定义。
5. 222. 完全二叉树的节点个数:利用完全二叉树特性的高效解法
5.1 完全二叉树的定义与性质
完全二叉树是指除了最后一层外,其他层的节点都达到最大数量,且最后一层的节点都集中在左侧。这种结构有两个重要性质:
- 高度为h的完全二叉树,节点数在[2^h, 2^(h+1)-1]之间
- 左子树的高度至少不小于右子树的高度
5.2 暴力解法与复杂度分析
最直接的方法是遍历整棵树计数:
python复制def countNodes(root):
if not root:
return 0
return 1 + countNodes(root.left) + countNodes(root.right)
这个解法时间复杂度为O(n),没有利用完全二叉树的特性。
5.3 优化解法:利用完全二叉树特性
我们可以利用完全二叉树的性质来优化:
python复制def countNodes(root):
if not root:
return 0
left_height = 0
node = root.left
while node:
left_height += 1
node = node.left
right_height = 0
node = root.right
while node:
right_height += 1
node = node.right
if left_height == right_height: # 满二叉树
return (2 << left_height) - 1
else:
return 1 + countNodes(root.left) + countNodes(root.right)
这个优化解法的时间复杂度为O(log n * log n)。原理是:
- 计算左右子树的最左高度
- 如果相等,说明左子树是满二叉树,可以直接计算节点数
- 否则,右子树是完全二叉树,可以递归计算
5.4 进一步优化:二分查找法
更进一步的优化是结合二分查找的思想:
python复制def countNodes(root):
if not root:
return 0
def exists(idx, depth, node):
left, right = 0, 2**depth - 1
for _ in range(depth):
mid = left + (right - left) // 2
if idx <= mid:
node = node.left
right = mid
else:
node = node.right
left = mid + 1
return node is not None
depth = 0
node = root
while node.left:
depth += 1
node = node.left
if depth == 0:
return 1
left, right = 1, 2**depth - 1
while left <= right:
mid = left + (right - left) // 2
if exists(mid, depth, root):
left = mid + 1
else:
right = mid - 1
return (2**depth - 1) + left
这个解法的时间复杂度也是O(log n * log n),但常数因子更小。它利用了完全二叉树最后一层的节点编号特性,通过二分查找确定最后一个存在的节点编号。
在实际面试中,面试官通常会期待你至少提出利用完全二叉树特性的解法(5.3节),如果能进一步讨论二分查找的优化思路(5.4节),会大大加分。
6. 二叉树算法实战经验分享
经过多年算法教学和面试经验,我发现二叉树相关的问题有几个常见的陷阱和优化点:
-
递归终止条件:总是忘记处理空节点的情况,导致无限递归。例如在计算高度时,空节点的高度应该是0而不是1。
-
重复计算:如平衡二叉树的第一个解法,大量重复计算子树高度。在面试中,指出这个问题并优化会展现你的分析能力。
-
空间复杂度:递归解法有隐式的调用栈空间,对于极度不平衡的树(如链表),空间复杂度可能达到O(n)。迭代解法有时可以优化这一点。
-
完全二叉树特性:很多同学会忽略题目给出的特殊条件(如完全二叉树),直接使用通用解法。识别并利用这些特性是算法优化的关键。
-
测试用例设计:在面试中写完代码后,应该主动提出测试不同情况的树:
- 空树
- 只有根节点的树
- 只有左子树或右子树的树
- 平衡和不平衡的树
- 完全和非完全的树
对于想要系统学习二叉树算法的同学,我建议按照以下顺序练习:
- 基本遍历(前序、中序、后序、层序)
- 递归性质问题(如最大深度、对称树)
- 路径相关问题(如路径总和、所有路径)
- 构造性问题(如从前序和中序构造二叉树)
- 特殊二叉树(如二叉搜索树、平衡二叉树、完全二叉树)
最后,二叉树算法是面试中的高频考点,掌握这些经典问题的解法不仅能帮助你在面试中表现出色,更能培养你的递归思维和算法优化能力。在实际开发中,树形结构也广泛应用于文件系统、DOM树、数据库索引等领域,这些算法基础会为你解决更复杂的问题打下坚实基础。
