目录
题目地址:
题目:
审题目+事例+提示:
解题方法:
解题分析:
解题思路:
代码实现:
补充说明:
代码优化:
代码实现(存储父节点) :
题目地址:
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
难度:中等
今天刷二叉树的最近公共祖先,大家有兴趣可以点上面链接,看看题目要求,试着做一下。
题目:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
我们直接看题解吧:
快速理解解题思路小建议:
可以先简单看一下解题思路,然后照着代码看思路,会更容易理解一些。
审题目+事例+提示:
·祖先:若节点p在节点root的左(右)子树中,或p=root,则称root是p的祖先。
·最近公共祖先:设节点root为节点p,q的某公共祖先,若其子节点root.left和右子节点root.right都不是p,q的公共祖先,则称root是‘最近的公共祖先’。
解题方法:
方法1:递归
方法2:存储父节点
解题分析:
由定义可知,若root是p,q的最近公共祖先,则可能的出现的情况如下:
·p和q在的root子树中,且分别在root的异侧(即分别在左右子树中)
·p=root,且q在root的左或右子树中;
·q=root,且p在root的左或右子树中;
这里可以通过递归对二叉树进行先序遍历,当遇到节点p或q时返回。
由底向上回溯,当节点p,q在节点root的异侧时,节点root即为最近公共祖先,则向上返回root.
解题思路:
1、终止条件:
当越过叶子节点,直接返回null
当root等于p,q,直接返回root
2、递推工作:
递归左子节点,返回值记为left
递归右子节点,返回值即为right
3、返回值(根据left和right),可分为四种情况:
1)当left和right同时为空,即root的左/右子树中不含p,q,返回null
2)当left和right不同时为空,即 p,q分别在root的异侧,因此root为最近公共祖先,返回root
3)当left为空,right不为空,即p,q都不在左子树,返回right:
·p,q其中一个在root的右子树,此时right指向p(q)
·p,q两个节点都在root的右子树,此时right指向最近公共祖先节点。
4)当left不为空,right为空,即p,q都不在右子树,返回left:具体与情况3)同理
注:
1、这样找出来的公共祖先深度是否是最大的?
其实是最大的,因为我们是自底向上从叶子节点开始更新的,所以在所有满足条件的公共祖先中一定是深度最大的祖先先被访问到,因此此时的公共祖先深度是最大的。
代码实现:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//当前根节点是p和q中的任意一个或节点为空,直接返回root
if(root == null || root == p || root == q) return root;
//根节点不是p和q中的任意一个,那么就继续分别往左子树和右子树找p和q
//或者说既然p 或 q 不是公共结点,那么分别递归左子树和右子树
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
//若左右子树的上p,q都没有找到,则直接返回null
if(left == null && right == null) return null; // 1.
//左子树没有p也没有q,就返回右子树的结果
if(left == null) return right; // 3.
//右子树没有p也没有q就返回左子树的结果
if(right == null) return left; // 4.
//左右子树找到p或q了,即p和q分别在左右子树上,此时最近公共祖先就是root
return root; // 2. if(left != null and right != null)
}
}
补充说明:
1、root为null有两种情况,一种是树为null,第二种是叶子结点为null,也就是遍历完了,也没找到目标值
代码优化:
仔细观察上面的可发现,情况 1)可合并到情况3)、4)中
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null) return right;
if(right == null) return left;
return root;
}
}
代码实现(存储父节点) :
思路
我们可以用哈希表存储所有节点的父节点,然后我们就可以利用节点的父节点信息从 p 结点开始不断往上跳,并记录已经访问过的节点,再从 q 节点开始不断往上跳,如果碰到已经访问过的节点,那么这个节点就是我们要找的最近公共祖先。
算法
从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
从 p 节点开始不断往它的祖先移动,并用数据结构记录已经访问过的祖先节点。
同样,我们再从 q 节点开始不断往它的祖先移动,如果有祖先已经被访问过,即意味着这是 p 和 q 的深度最深的公共祖先,即 LCA 节点。
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;
}
}