0
点赞
收藏
分享

微信扫一扫

LeetCode. 236二叉树的最近公共祖先



文章目录

  • ​​题目描述​​
  • ​​思路​​
  • ​​扩展​​

题目描述

给定一颗二叉树,以及2个指定节点​​p​​​ , ​​q​​,找到这2个节点的最近公共祖先。注意,一个节点也可以是它自己的祖先。并且根据提示,有​​p != q​​​,并且​​p​​​和​​q​​均存在于二叉树中。

思路

由于​​p​​​和​​q​​一定存在于树中,则其最近公共祖先一定存在。其最近公共祖先有如下2种情况

  • 最近公共祖先是​​p​​​或者​​q​
  • 最近公共祖先不是​​p​​​或者​​q​

对于情况1,假设最近公共祖先是​​p​​​,则​​q​​​一定存在于​​p​​​的左子树或右子树,此时直接返回​​p​​​即可。当最近公共祖先是​​q​​时同理。

对于情况2,假设最近公共祖先是​​r​​​,则​​p​​​和​​q​​​一定分别位于​​r​​的左子树和右子树上。

由于求解的是这种节点的父子关系,很容易想到需要用DFS+回溯的方式。我们期望在DFS的过程中,能够把节点​​p​​​和​​q​​的信息,通过回溯传递上来。

所以我们这样来定义递归函数:

用​​dfs(node)​​ 这个函数表示,对以​​node​​为根节点的二叉树进行搜索,若​​p​​在树中出现,则返回​​p​​;若​​q​​在树中出现,则返回​​q​​;若​​p​​和​​q​​同时在树中出现,则返回​​p​​和​​q​​的最近公共祖先。

伪代码为:

dfs(node):
if (node == null) return null; // 跨过叶子节点
if (node == p || node == q) return node; // 找到p或者q
left = dfs(node.left); // 查找node的左子树, 看p或q是否出现
right = dfs(node.right); // 查找node的右子树, 看p或q是否出现
if (left != null && right != null) return node; // 当 p 和 q 分别在 node 的左子树和右子树出现, 则最近公共祖先就是 node
if (left != null) return left; // 若左子树上找到了p或q, 返回左子树的查找结果
return right; // 右子树上找到了p或q,返回右子树的查找结果

假设最近公共祖先不是​​p​​​或​​q​​​,是​​r​​​。并且假设​​p​​​位于​​r​​​左子树,​​q​​​位于​​r​​​右子树。则通过对​​r.left​​​进行dfs搜索,能够把​​p​​​的节点信息传递上来,对​​r.right​​​进行dfs搜索,能够把​​q​​的节点信息传递上来。并且r节点,是唯一一个对其左子树进行dfs有结果,并且对其右子树进行dfs也有结果的节点。(​​r​​​节点下面的节点,最多只可能在某一侧的dfs中有结果;​​r​​​节点上面的节点,只会在​​r​​所在的这一侧的子树中,有dfs的结果)。

假设最近公共祖先是​​p​​​或​​q​​​,则同样在遇到​​p​​​或​​q​​时,dfs函数就提前返回了,和本题的答案相一致。

注意该方法中,对递归函数​​dfs​​的定义,其实是带有扩展的,它求解的是:

  • 若​​p​​​出现在二叉树中,则返回​​p​
  • 若​​q​​​出现,则返回​​q​
  • 若​​p​​​和​​q​​同时出现,则返回二者的最近公共祖先
  • 若​​p​​​和​​q​​​都不出现,返回​​null​

而由于本题中p​q​一定存在于二叉树中,所以该方法能求出正确结果。

​dfs​​​函数在整个搜索过程中的作用是,将​​p​​​或​​q​​的信息,通过返回值,传递上来(从子节点,往上传递给父节点,回溯)。

代码如下:

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 && right != null) return root;
return left != null ? left : right;
}
}

扩展

假设​​p​​和​​q​​不一定都存在于二叉树当中,那么上面的方法就无法求出正确结果了。

我们换一种思路,我们设一个外部变量​​ans​​,来保存在​​dfs​​过程中可能搜到的答案,并且这样定义​​dfs​​函数:

​dfs(node)​​表示:在以​​node​​为根节点的二叉树中进行搜索,若树中出现了​​p​​或者​​q​​,则返回​​true​​,否则返回​​false​​。

我们从根节点​​root​​开始进行DFS:

  • 当​​root​​​等于​​p​​​或​​q​​​时,只需要递归的搜索​​root​​​的左子树和右子树,只要其中一侧的​​dfs​​​返回了​​true​​​,则说明找到了另一个节点,此时更新​​ans = root​​,然后提前返回即可(最近公共祖先是p或者​q​的情况
  • 当​​root​​​不等于​​p​​​或​​q​​​时,递归搜索两侧,只有当两侧的​​dfs​​​都返回了​​true​​​时,更新​​ans = root​​(最近公共祖先不是p或者​q​的情况,此时的​​root​​​节点,是唯一一个对其左右子树调用​​dfs​​​都返回​​true​​的节点)
  • 当​​ans != null​​ 时,说明已经找到答案,直接提前返回

另外要注意的是,假设​​root = p​​,且对​​root​​的子树调用​​dfs​​返回​​true​​,则说明在​​root​​子树中找到的一定是节点​​q​​;类似地,假设​​root​​不为​​p​​也不为​​q​​,若对​​root​​的左子树和右子树分别调用​​dfs​​都返回​​true​​,则一定是​​p​​和​​q​​分别位于​​root​​的左右子树当中。

也就是说,当找到了一个节点时,对另外一边调用​​dfs​​如果返回​​true​​,则一定是找到了另外一个节点。(同一个节点不可能存在于一棵树上的两个位置)

这种解法能够在​​p​​​或 ​​q​​​不存在于二叉树的情况下,返回正确结果(​​null​​)。

class Solution {
TreeNode ans = null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root, p, q);
return ans;
}

private boolean dfs(TreeNode cur, TreeNode p, TreeNode q) {
if (cur == null || ans != null) return false; // 跨过叶子节点, 或者已经找到答案, 直接返回
if (cur == p || cur == q) {
if (dfs(cur.left, p, q) || dfs(cur.right, p, q)) {
ans = cur;
}
return true;
}
boolean left = dfs(cur.left, p, q);
boolean right = dfs(cur.right, p, q);
if (left && right) {
ans = cur;
}
return left || right;
}
}



举报

相关推荐

0 条评论