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

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; // 正常情况
        }
    }
    原文作者:酸辣粉_2329
    原文地址: https://www.jianshu.com/p/c48d97547b94
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞