二叉树最小公共祖先Java,[LeetCode 236] LCA of Binary Tree 二叉树中的最低公共祖先

这篇博客介绍了如何使用深度优先搜索(DFS)解决LeetCode上的一个问题,即寻找二叉树中两个指定节点的最低公共祖先(LCA)。首先,作者提出了一种简单但效率较低的解决方案,通过获取两个节点的路径并从上到下查找共同节点。然后,优化了算法,通过带有返回值的DFS递归,在遍历过程中直接确定LCA,降低了空间复杂度到O(1)。此外,还讨论了处理特殊情况,如节点可能不在树中的情况。
摘要由CSDN通过智能技术生成

LeetCode链接

给一个root节点,再给定另外两个node,返回这两个node在root代表的这棵树中最低的公共祖先(LCA)。

1

/ \

2 3

/ \ \

4 5 6

节点4和节点5的LCA是节点2

节点2和节点5的LCA是节点2

以此类推

用DFS解决这道题感觉再好不过了

第一个想法是:

找出从root到两个节点分别的路径。

然后再从这两条路径中,从上到下找共同的节点。

其实这种想法有点Backtrack的意思,时间复杂度相对比较高。居然LeetCode还是通过了,只是Runtime惨不忍睹……代码还是贴出来,因为有助于思考嘛。

复杂度:

Time: O(N) *虽然是O(N)级别的,但是还是很大……

Space: O(N)

public class Solution {

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

if (root == null || p == null || q == null) {

return null;

}

List path1 = getPath(root, p);

List path2 = getPath(root, q);

TreeNode lca = null;

// find LCA from top to down

for (int i = 0; i < Math.min(path1.size(), path2.size()); i++) {

if (path1.get(i) == path2.get(i)) {

lca = path1.get(i);

}

}

return lca;

}

// find path from root to target

private List getPath(TreeNode root, TreeNode target) {

List path = new ArrayList<>();

helper(root, target, new ArrayList(), path);

return path;

}

// dfs traversal

private void helper(TreeNode current, TreeNode target, List path, List realPath) {

if (current == null) {

return;

}

if (current == target) {

path.add(target);

realPath.addAll(path);

return;

}

path.add(current);

helper(current.left, target, path, realPath);

helper(current.right, target, path, realPath);

path.remove(path.indexOf(current));

}

}

优化

然后想了想,空间应该可以优化,时间可以更接近于真正的O(N)。

DFS递归有两种情况:

不带返回值的递归

带返回值的递归

在上面的解法中,递归函数是没有返回值的,因为记录path的任务赋予了realPath这个引用值,所以并不需要在递归的过程中返回任何值(递归上一层并不需要下一层的信息)

先来一个例子:

1

/ \

2 3

/ \ \

4 5 6

\

7

找出4和7的LCA,答案是2

找出2和4的LCA,答案是2

这里就有两种情况:

两个node在某个节点的两侧

两个node其中的一个是两者的LCA

对于第一个情况来说:

假如当程序运行到4(pointer = 4),发现4是一个target节点,返回给上一级2说“我找到一个了,它是4”

然后DFS到7的时候,又返回给5这一层,说“我找到7了”,而5又接着往上返回给2说“我下面有个人说找到7了”

这个时候,2知道它左右两边都找到了,说明自己就是LCA,它就往上返回说“返回2就是了”……

这个时候,1发现自己的左子树返回了一个2,右子树什么都没有返回,那么它就返回2,至此,2就是答案。

** 这样的解法就需要递归返回值了 **

把分析抽象出来:

对于pointer(遍历到当前的节点),

当pointer左子树返回值不为空,右子树返回值也不为空,可以得出root的左右子树分别包含了两个target节点,返回pointer

当pointer左右子树其中一个的返回值为空,返回左右子树中不为空的那个值。

代码:

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

if (root == null || p == root || q == root) {

return root;

}

TreeNode left = lowestCommonAncestor(root.left, p, q);

TreeNode right = lowestCommonAncestor(root.right, p, q);

if (left != null && right != null) {

return root;

}

return left == null ? right : left;

}

复杂度

Time: O(N)

Space: O(1)

另外,题目默认两个给定的节点一定在树里,万一不保证这一点,就需要增加一个判断两个点在不在树中的条件。

暂时想到的是如果根节点这一层,不为null的子树返回的值等于给定的两个node中的一个,那么就说明至少有一个node不在树中。如果都为null,那说明两个都不在。

if (left != null) {

if (left == p || left == q) {

// p或者q不在树里

} else {

return right; // 正常情况

}

} else {

if (right == p || right == q) {

// p或者q不在树里

} else {

return left; // 正常情况

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值