在数据结构与算法领域,二叉树是最基础也是最重要的非线性数据结构之一。今天我们要探讨的是一个看似简单但容易出错的经典问题:如何计算二叉树的直径?这个问题在技术面试中出现频率很高,也是检验程序员对二叉树理解深度的试金石。
二叉树的直径被定义为树中任意两个节点间最长路径的边数。这个定义看似简单,但有几个关键点需要特别注意:
来看一个反例:
code复制 1
/ \
2 3
/ \
4 5
/ \
6 7
这棵树的左子树高度为3,右子树高度为1,但直径是5(路径6-4-2-5-7),而不是3+1=4。
最直观的解法是采用深度优先搜索(DFS)遍历每个节点,计算通过该节点的最长路径,然后取最大值。具体步骤如下:
python复制class Solution:
def diameterOfBinaryTree(self, root: TreeNode) -> int:
self.diameter = 0
def dfs(node):
if not node:
return 0
left = dfs(node.left)
right = dfs(node.right)
self.diameter = max(self.diameter, left + right)
return max(left, right) + 1
dfs(root)
return self.diameter
这个算法的时间复杂度是O(n),因为每个节点只被访问一次。空间复杂度在最坏情况下(树退化为链表)是O(n),平均情况下是O(log n)。
注意:在递归实现中,我们使用了一个实例变量diameter来记录最大值。也可以使用非局部变量或其他方式传递这个值,但要小心Python的作用域规则。
虽然递归解法简洁明了,但在实际工程中,我们可能需要考虑使用迭代法来避免递归带来的栈溢出风险。以下是使用后序遍历的迭代实现:
python复制def diameterOfBinaryTree(root):
if not root:
return 0
diameter = 0
stack = [(root, False)]
height = {}
while stack:
node, visited = stack.pop()
if visited:
left = height.get(node.left, 0)
right = height.get(node.right, 0)
diameter = max(diameter, left + right)
height[node] = max(left, right) + 1
else:
stack.append((node, True))
if node.right:
stack.append((node.right, False))
if node.left:
stack.append((node.left, False))
return diameter
这个实现使用了哈希表来记录每个节点的高度,通过模拟系统调用栈的方式实现了后序遍历。虽然代码稍长,但在处理深度很大的树时更加安全。
在实际编码中,我们需要考虑以下边界条件:
python复制# 边界条件测试用例
def test_boundary_cases():
# 空树
assert diameterOfBinaryTree(None) == 0
# 只有根节点
assert diameterOfBinaryTree(TreeNode(1)) == 0
# 完全左斜
left_tree = TreeNode(1, TreeNode(2, TreeNode(3)))
assert diameterOfBinaryTree(left_tree) == 2
# 完全右斜
right_tree = TreeNode(1, None, TreeNode(2, None, TreeNode(3)))
assert diameterOfBinaryTree(right_tree) == 2
无论是递归还是迭代实现,我们的算法都遵循标准的DFS遍历模式,每个节点仅被访问一次。因此时间复杂度为O(n),其中n是树中的节点数。这是最优解,因为要计算直径必须访问所有节点。
空间复杂度取决于树的形状:
对于迭代实现,我们使用了显式的栈结构,但最坏情况下的空间复杂度仍然是O(n)。
虽然这个问题的标准解法已经相当高效,但在特定场景下还可以考虑以下优化:
二叉树直径问题看似学术,但在实际工程中有重要应用:
基于这个基础问题,面试中常出现以下变种:
以加权直径为例,解法只需稍作修改:
python复制def diameterOfBinaryTree(root):
self.max_diameter = 0
def dfs(node):
if not node:
return 0
left = dfs(node.left)
right = dfs(node.right)
# 假设node.left和node.right有权重属性
left_len = left + (node.left.weight if node.left else 0)
right_len = right + (node.right.weight if node.right else 0)
self.max_diameter = max(self.max_diameter, left_len + right_len)
return max(left_len, right_len)
dfs(root)
return self.max_diameter
解决二叉树直径问题的核心思路可以归纳为:
这种思路可以推广到许多其他树形问题,如最大路径和、最长同值路径等。关键在于定义好递归函数的返回值意义(这里返回的是高度)和如何利用子问题的解来构建当前问题的解。
在编写这类递归代码时,我通常会先明确三点:
这种结构化思维可以帮助快速写出正确的递归算法,避免陷入递归调用的细节泥潭。