1. 问题背景与核心概念
在二叉树相关算法题中,"计算两个节点之间的距离"是一个经典且实用的题目。这个问题看似简单,但涉及多个基础概念的组合运用。我们先明确几个关键术语:
- 二叉树节点距离:定义为两个节点之间最短路径的边数。例如,根节点到其左子节点的距离为1。
- LCA(最低公共祖先):两个节点在树中最近的共同祖先节点,这是计算节点距离的关键中间结果。
- 节点深度:从根节点到该节点的路径边数,这是距离计算的基础参数。
实际场景中,这个问题可以应用于:
- 家谱系统中计算亲属关系远近
- 组织架构中衡量部门间的协作距离
- 网络路由中跳数的计算
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法思路解析
2.1 基础解法:分步计算法
最直观的解法分为三个步骤:
- 找到两个节点的LCA
- 分别计算LCA到两个节点的距离
- 将两个距离相加
这个方法的优势是逻辑清晰,时间复杂度为O(n)(n为节点数),空间复杂度取决于递归深度。
2.2 优化思路:一次遍历法
更高效的实现可以在单次遍历中完成所有计算:
- 后序遍历二叉树
- 在遍历过程中记录当前深度
- 当找到目标节点时,保存其深度和路径信息
- 比较两个节点的路径差异
这种方法减少了重复遍历,但实现复杂度较高。
3. 代码实现详解
3.1 Python实现示例
python复制class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def findLCA(root, p, q):
if not root or root == p or root == q:
return root
left = findLCA(root.left, p, q)
right = findLCA(root.right, p, q)
if left and right:
return root
return left if left else right
def nodeDepth(root, node, depth=0):
if not root:
return -1
if root == node:
return depth
left = nodeDepth(root.left, node, depth+1)
if left != -1:
return left
return nodeDepth(root.right, node, depth+1)
def distanceBetweenNodes(root, p, q):
lca = findLCA(root, p, q)
return nodeDepth(lca, p) + nodeDepth(lca, q)
3.2 关键点说明
- LCA查找:采用递归后序遍历,当左右子树分别包含目标节点时,当前节点即为LCA
- 深度计算:递归过程中维护当前深度,匹配到目标节点时返回累计深度
- 距离合成:将两个节点到LCA的距离相加即为最终结果
4. 复杂度分析与优化
4.1 时间复杂度
- 最坏情况:O(n)(树退化为链表)
- 平均情况:O(n)(需要遍历所有节点)
4.2 空间复杂度
- 递归栈空间:O(h)(h为树高)
- 迭代实现可优化为O(1)额外空间
4.3 实际测试建议
对于大规模树结构:
- 考虑使用非递归实现
- 可以预先建立父节点指针表
- 对频繁查询场景,建议预处理所有节点的深度信息
5. 常见问题与解决方案
5.1 节点不存在的情况
问题:当输入节点不在树中时,算法会返回错误结果
解决方案:
python复制def validateNodes(root, p, q):
# 先验证节点是否存在
def contains(root, node):
if not root: return False
return root == node or contains(root.left, node) or contains(root.right, node)
return contains(root, p) and contains(root, q)
5.2 重复计算优化
问题:基础实现中LCA查找和深度计算存在重复遍历
优化方案:可以修改LCA查找函数,在查找过程中同时记录深度信息
5.3 非递归实现
对于深度较大的树,递归可能导致栈溢出。以下是迭代实现示例:
python复制def distanceBetweenNodesIterative(root, p, q):
parent = {root: None}
stack = [root]
# 建立父指针映射
while p not in parent or q not in parent:
node = stack.pop()
if node.left:
parent[node.left] = node
stack.append(node.left)
if node.right:
parent[node.right] = node
stack.append(node.right)
# 获取p的祖先路径
ancestors = set()
while p:
ancestors.add(p)
p = parent[p]
# 找到q的路径中第一个公共祖先
distance = 0
while q not in ancestors:
q = parent[q]
distance += 1
# 计算p到LCA的距离
p = q
while p in parent and parent[p]:
p = parent[p]
distance += 1
return distance
6. 实际应用扩展
6.1 多节点距离计算
当需要计算多个节点对的距离时,可以:
- 预处理所有节点的深度和父节点信息
- 建立快速查询的数据结构
- 使用Tarjan离线算法优化批量查询
6.2 加权树距离
对于边带有权重的树:
- 将深度计算改为累计权重
- 其他算法逻辑保持不变
- 注意权重的方向性影响
6.3 在N叉树中的应用
算法可以推广到N叉树:
- LCA查找逻辑相同
- 深度计算需要遍历所有子节点
- 时间复杂度变为O(n×k)(k为最大分支数)
7. 面试技巧与注意事项
- 明确问题:先确认树的类型(普通二叉树/二叉搜索树)、节点是否一定存在等边界条件
- 画图分析:在纸上画出示例树,标注节点关系
- 分步讲解:先说明LCA概念,再解释距离计算
- 复杂度分析:主动分析时间/空间复杂度
- 测试用例:
- 两个节点是父子关系
- 两个节点在不同子树
- 一个节点是根节点
- 节点不存在的情况
8. 同类问题延伸
掌握这个算法后,可以解决以下变种问题:
- 查找二叉树中任意两节点的最长路径
- 计算所有叶节点之间的平均距离
- 找出距离为k的所有节点
- 判断树是否为平衡二叉树(基于高度差)
在实际编码中,我发现使用递归实现虽然代码简洁,但对于特别深的树可能存在栈溢出风险。建议在工程实现中:
- 对于已知深度不大的树,优先使用递归
- 对于用户输入的树结构,使用迭代实现更安全
- 可以添加最大递归深度保护
