给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
二、解决
1、暴力之哈希迭代
思路: 直观思路是由p、q出发去寻找父节点,该路径相交的最近节点是最近公共祖先。
但这题没有双向指针,不过父子关系可以用Map<root.child.val, root>来存储,把root到所有节点的路径都存下来,其中包含root–>p的path1与root–>q的path2。
然后遍历path1,由p.val出发,到root结束,把沿途所有val存到Set<Integer>中;然后再遍历path2,从q.val出发,判断Set()中是否包含沿途节点的val,如果包含,则说明该节点是最近公共祖先。
可能有点绕,举个例子:
求: 节点2、3的最近公共祖先。
Step1: 存下父子关系。<p.val, root> and <q.val, root>,即<2, 1> and <3, 1>。
Step2.1: 由p出发,访问从p-->root的沿途所有节点的值,并存入set中。
在这是把2与1存入set, 代表访问了,即set(2, 1)。
Step2.2: 再从q出发,访问q-->root的沿途所有节点的值,判断节点值在set()中是否存在,存在,则说明该节点是最近公共祖先。
在这是,先看3,没在set(2,1)中,再看1,在其中,说明1是2,3的最近公共祖先。
Step3: 返回节点1。
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
Map<Integer, TreeNode> parent = new HashMap<Integer, TreeNode>();
Set<Integer> visited = new HashSet<Integer>();
public void dfs(TreeNode root) {
if (root.left != null) {
parent.put(root.left.val, root);
dfs(root.left);
}
if (root.right != null) {
parent.put(root.right.val, root);
dfs(root.right);
}
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root);
while (p != null) {
visited.add(p.val);
p = parent.get(p.val);
}
while (q != null) {
if (visited.contains(q.val)) {
return q;
}
q = parent.get(q.val);
}
return null;
}
}
时间复杂度: O(n)
空间复杂度: O(n)
2、递归
思路: 跳出来一点,从左、右子树的宏观视角去思考问题。
从根节点出发,在左右子树中去寻找包含节点p或q,如果返回结果同时不为空,则说明该root是最近公共祖先。
递归有点不太好理解,建议自己用上面的例子1-[2,3],自己推演下。
代码:
class Solution {
// 作用:以root为根的二叉树,去子树中寻找p/q
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 为空或存在一个,就返回那个节点p/q. 为空:代表没找到,不为空,代表递归找到了子节点
if(root == null || root == p || root == q) return root;
// root!=null && root!=p && root!=q, 就继续去左右子树中去寻找
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 如果左右子树均不为空,说明找到了,则返回root
if(left != null && right != null) return root;
// 如果有一个为空,说明返回为空的那个子树不包含p/q节点。我们就返回非空的那个子树,继续向下寻找
return left != null ? left : right;
}
}
时间复杂度: O(n)
空间复杂度: O(n)
三、参考
1、My Java Solution which is easy to understand
2、Java/Python iterative solution
3、Java iterative and recursive solutions.
4、二叉树的最近公共祖先