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<TreeNode> path1 = getPath(root, p);
List<TreeNode> 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<TreeNode> getPath(TreeNode root, TreeNode target) {
List<TreeNode> path = new ArrayList<>();
helper(root, target, new ArrayList<TreeNode>(), path);
return path;
}
// dfs traversal
private void helper(TreeNode current, TreeNode target, List<TreeNode> path, List<TreeNode> 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; // 正常情况
}
}