一、题目
力扣原题:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
二、递归
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (null == root || null == p || null == q) {
return null;
}
// 若当前节点为p节点或q节点,则当前节点即“最近公共祖先”,直接返回即可(剪枝)
if (root.val == p.val || root.val == q.val) {
return root;
}
// 判断左子树是否存在p节点或q节点
TreeNode left = null;
if (null != root.left) {
left = lowestCommonAncestor(root.left, p, q);
}
// 判断右子树是否存在p节点或q节点
TreeNode right = null;
if (null != root.right) {
right = lowestCommonAncestor(root.right, p, q);
}
// 若左右子树均存在p节点或q节点,则当前节点即“最近公共祖先”
if (null != left && null != right) {
return root;
}
// 若左子树或右子树存在p节点或q节点,则此节点即“最近公共祖先”,直接返回即可
// 若左右子树均不存在p节点或q节点,则不存在“最近公共祖先”,返回null
return left != null ? left : right;
}
}
- 基本思路:
- 当前节点为p节点或q节点时,当前节点即“最近公共祖先”;
- 当前节点的左子树、右子树均存在p节点或q节点时,说明两个目标节点在当前节点的两侧,当前节点即“最近公共祖先”;
- 当前节点的左子树或右子树存在p节点或q节点,则此节点即“最近公共祖先”;
- 当前节点的左右子树均不存在p节点或q节点,说明不存在“最近公共祖先”;
- 时间复杂度:本质是DFS深搜整棵二叉树,查找p节点和q节点
- 最优:O(1)。根节点即p节点或q节点,此时根节点即“最近公共祖先”,无需递归左右子树;
- 最差:O(n)。遍历二叉树的所有节点;
- 平均:O(n)。
- 空间复杂度:主要体现在递归的栈空间消耗,栈的深度即二叉树的深度
- 最优:O(log(n + 1))。最好的情况下,除了最底层,其他层节点的左右子树均不为空,此时二叉树具有最小深度;
- 最差:O(n)。退化为链表时;
- 平均:O(log(n));
二、路径标记法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 保存所有节点的父节点
private Map<TreeNode, TreeNode> map;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (null == root || null == p || null == q) {
return null;
}
// 深搜二叉树,保存所有节点的父节点
map = new HashMap<TreeNode, TreeNode>();
dfs(root);
// 从p节点开始,标记其所有的直接、间接父节点
Set<TreeNode> set = new HashSet<>();
while (null != p) {
set.add(p);
p = map.get(p);
}
// 从q节点开始,访问其所有直接、间接父节点,并判断其标记位
while (null != q) {
if (set.contains(q)) {
return q;
}
q = map.get(q);
}
return null;
}
private void dfs(TreeNode node) {
// 递归遍历左子树
if (null != node.left) {
map.put(node.left, node);
dfs(node.left);
}
// 递归遍历右子树
if (null != node.right) {
map.put(node.right, node);
dfs(node.right);
}
}
}
- 基本思路:
- 通过一轮DFS深搜二叉树,记录每个节点的直接父节点,存储在Map中;
- 由p节点开始,访问其所有的直接、间接父节点,并通过Set标记出来(p节点到root节点的路径);
- 由q节点开始,访问其所有的直接、间接父节点,并判断当前节点是否被标记过(q节点到root节点的路径);
- “最近公共祖先”即p节点到root节点、q节点到root节点这两条路径的交点;
- 时间复杂度:O(n)。DFS深搜遍历二叉树的时间复杂度为O(n),p节点、q节点路径遍历的时间复杂度均小于O(n),因此时间复杂度为O(n)。
- 空间复杂度:O(n)。DFS深搜的空间复杂度等于二叉树的高度,最优为O(log(n)),最差为O(n)。存储所有节点的直接父节点的Map结构的空间复杂度为O(n),因此空间复杂度为O(n)。
四、总结
- 二叉树的遍历问题可以通过DFS深搜解决;
- 二叉树问题经常可以转化为递归问题(解法一);
- 根据题意,通过绘图我们可以发现p节点、q节点的“最近公共祖先”即是这两个节点到root节点路径的交点。但将这个思想转化为程序表达的技巧实在是巧妙!
本文详细解析了二叉树中寻找最近公共祖先的两种方法:递归法和路径标记法,包括每种方法的基本思路、时间复杂度和空间复杂度分析。递归法通过深度优先搜索解决问题,路径标记法则利用DFS记录节点路径,找到两条路径的交点作为最近公共祖先。
402

被折叠的 条评论
为什么被折叠?



