
二叉树最近公共祖先:递归法与栈存路径法详解及代码实现
针对二叉树最近公共祖先问题,解析了两种核心解法:递归法利用自底向上回溯简化代码,栈存路径法通过深度优先搜索记录路径并对比寻找第一个相同节点。提供了完整的 Java 代码实现,帮助读者根据场景灵活选择高效求解方案。

针对二叉树最近公共祖先问题,解析了两种核心解法:递归法利用自底向上回溯简化代码,栈存路径法通过深度优先搜索记录路径并对比寻找第一个相同节点。提供了完整的 Java 代码实现,帮助读者根据场景灵活选择高效求解方案。





通过深度优先搜索,分别找到从根节点到 p 和 q 的路径中所有节点,并分别存入两个栈中
比较两个栈中元素长度,将较长的栈中元素弹出,然后比较两个栈中的下一深度元素是否相等
同时弹出两个栈中不相等的栈顶元素,第一个相同的节点就是最近公共祖先

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 当前节点为空,直接返回 null
if (root == null) {
return null;
}
// 当前节点是 p 或者 q,返回当前节点(祖先)
if (root == p || root == q) {
return root;
}
// 递归探索左子树,找到 p 和 q 的最近祖先
TreeNode leftRet = lowestCommonAncestor(root.left, p, q);
// 递归探索右子树,找到 p 和 q 的最近祖先
TreeNode rightRet = lowestCommonAncestor(root.right, p, q);
// 左子树和右子树都找到
if (leftRet != null && rightRet != null) {
return root;
}
// 左子树找到
if (leftRet != null) {
return leftRet;
} else {
// 右子树找到
return rightRet;
}
}
public boolean getPath(TreeNode root, TreeNode node, Stack<TreeNode> stack) {
if (root == null) {
return false;
}
// 将当前节点压入路径
stack.push(root);
// 找到目标节点
if (root == node) {
return true;
}
// 递归搜索左子树
boolean flg = getPath(root.left, node, stack);
if (flg) {
return true;
}
// 递归搜索右子树
flg = getPath(root.right, node, stack);
if (flg) {
return true;
}
// 左右子树都没找到,弹出当前节点
stack.pop();
return false;
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
// 找到跟到 p 和 q 的路径
Stack<TreeNode> stackp = new Stack<>();
Stack<TreeNode> stackq = new Stack<>();
getPath(root, p, stackp);
getPath(root, q, stackq);
// 对齐两个栈长度
int sizep = stackp.size();
int sizeq = stackq.size();
int size = sizep - sizeq;
if (size > 0) {
// stacp 更长,弹出多余元素
while (size != 0) {
stackp.pop();
size--;
}
} else {
// stacq 更长,弹出多余元素
size = sizeq - sizep;
while (size != 0) {
stackq.pop();
size--;
}
}
// 此时两个栈大小一样,
// 同时弹出栈顶,找到相同的
while (!stackp.isEmpty() && !stackq.isEmpty()) {
if (stackp.peek().equals(stackq.peek())) {
return stackp.peek();
}
stackp.pop();
stackq.pop();
}
return null;
}
通过递归法的'自底向上'回溯,以及栈存路径法的'路径对比'思路,我们可以高效求解二叉树的最近公共祖先问题——前者利用递归特性简化代码,后者通过显式路径存储更直观易懂。结合本文的思路分析与代码示例,你可以根据场景灵活选择解法,轻松应对这类二叉树算法题。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online