一、问题

  求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树)。最近公共祖先简称LCA(Lowest Common Ancestor)。例如,如下图一棵普通的二叉树。

    

  结点3和结点4的最近公共祖先是结点2,即LCA(3,4)=2 。在此,需要注意到当两个结点在同一棵子树上的情况,如结点3和结点2的最近公共祖先为2,即 LCA(3,2)=2。同理:LCA(5,6)=4,LCA(6,10)=1。

  明确了题意,咱们便来试着解决这个问题。直观的做法,可能是针对是否为二叉查找树分情况讨论,这也是一般人最先想到的思路。除此之外,还有所谓的Tarjan算法、倍增算法、以及转换为RMQ问题(求某段区间的极值)。后面这几种算法相对高级,不那么直观,但思路比较有启发性,留作以后了解一下也有裨益。

二、思路

  解法一:暴力解法,在有parent指针的情况下,对两个节点依次向上回溯,直到两个节点相同,那么这个节点就是最近公共祖先。时间复杂度为O(n²)

  解法二:链表的交叉,在有parent指针的情况下,对两个节点分别到根节点的路径上的节点形成两个链表,因为两个链表很大可能不一样长,然后我们可以对其中长的一个链表从开头进行裁剪,形成两个链表长度一样,然后遍历直到相等,虽说优化了一点,但本质上还是暴力破解。

  解法三:借用数组和列表,在没有parent指针的情况,我们只能从根节点往下遍历,而不能进行往上回溯。所以可以借用数组或列表来保存数据,后面进行比对。和链表的交叉差不多。

  解法四:不借用额外的数据结构,没有parent指针。大概思路呢就是如果两个节点分属在根节点的两边,返回根节点,如果两个节点同在左子树或右子树,递归求解。

  解法五:这种解法现在不太懂,现在留作记录以后观看。其中解法四和解法五在代码中体现。

三、代码

 import java.util.ArrayList;
import java.util.List; public class LCA {
public int getLCA(int a, int b) {
TreeNode<Integer> root = of(10); TreeNode<Integer> lca = getLCA2(root, new TreeNode<Integer>(a), new TreeNode<Integer>(b));
return lca == null ? -1 : lca.val;
} //=====解法四===========
// 看两个节点是否在同一侧
private TreeNode<Integer> getLCA(TreeNode<Integer> root, TreeNode<Integer> p, TreeNode<Integer> q) {
if (root == null)
return null;
if (root.equals(p) || root.equals(q))
return root; boolean is_p_on_left = cover(root.left, p);
boolean is_q_on_right = cover(root.right, q);
if (is_p_on_left == is_q_on_right) {// 在root的两端
return root;
} else if (is_p_on_left) {// 在root的左端
return getLCA(root.left, p, q);
} else {
return getLCA(root.right, p, q);
}
} // 解法五
// 很难理解 递归定义不明确 第一次看到
private TreeNode<Integer> getLCA2(TreeNode<Integer> root, TreeNode<Integer> p, TreeNode<Integer> q) {
if (root == null)
return null;
if (root.equals(p) && root.equals(q))
return root; // x是lca,或者是p(p在这一侧),或者是q(q在这一侧),或者是null(pq都不在这一侧)
TreeNode<Integer> x = getLCA2(root.left, p, q);
if (x != null && !x.equals(p) && !x.equals(q)) {// 在左子树找到了lca
return x;
} TreeNode<Integer> y = getLCA2(root.right, p, q);
if (y != null && !y.equals(p) && !y.equals(q)) {// 在右子树找到了lca
return y;
} // x:p,q,null y :q,p,null
if (x != null && y != null) {// 一边找着一个
return root;
} else if (root.equals(p) || root.equals(q)) {
return root;
} else {
return x == null ? y : x;// 有一个不为null,则返回,都为null,返回null
}
} /**
* 判断x节点是否在n所代表的子树中
*
* @param n
* @param x
* @return
*/
private boolean cover(TreeNode<Integer> n, TreeNode<Integer> x) {
if (n == null)
return false;
if (n.equals(x))
return true;
return cover(n.left, x) || cover(n.right, x);
} public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 发现目标节点则通过返回值标记该子树发现了某个目标结点
if (root == null || root.equals(p.val) || root.equals(q))
return root;
// 查看左子树中是否有目标结点,没有为null
TreeNode left = lowestCommonAncestor(root.left, p, q);
// 查看右子树是否有目标节点,没有为null
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 都不为空,说明左右子树都有目标结点,则公共祖先就是本身
if (left != null && right != null)
return root;
// 如果发现了目标节点,则继续向上标记为该目标节点
return left == null ? right : left;
} static TreeNode<Integer> of(int n) {
List<TreeNode<Integer>> list = new ArrayList<TreeNode<Integer>>();
for (int i = 0; i < n; i++) {
list.add(new TreeNode<Integer>(i + 1));
}
for (int i = 0; i < n; i++) {
TreeNode<Integer> parent = list.get(i);
if (i * 2 + 1 < n) {
TreeNode<Integer> left = list.get(i * 2 + 1);
parent.left = left;
left.parent = parent;
} else
break;
if (i * 2 + 2 < n) {
TreeNode<Integer> right = list.get(i * 2 + 2);
parent.right = right;
right.parent = parent;
}
}
return list.get(0);
} private static class TreeNode<T> {
public T val;
public TreeNode<T> left = null;
public TreeNode<T> right = null;
TreeNode<T> parent; public TreeNode(T val) {
this.val = val;
} @Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false; TreeNode<?> treeNode = (TreeNode<?>) o; return val != null ? val.equals(treeNode.val) : treeNode.val == null;
} @Override
public int hashCode() {
return val != null ? val.hashCode() : 0;
}
}
}

查找最近公共祖先(LCA)的更多相关文章

  1. 最近公共祖先LCA(Tarjan算法)的思考和算法实现

    LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...

  2. 算法详解之最近公共祖先(LCA)

    若图片出锅请转至here 概念 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节 ...

  3. 最近公共祖先LCA(Tarjan算法)的思考和算法实现——转载自Vendetta Blogs

    LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...

  4. Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)

    Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...

  5. POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)

    POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...

  6. POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)

    POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...

  7. [模板] 最近公共祖先/lca

    简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...

  8. 【lhyaaa】最近公共祖先LCA——倍增!!!

    高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...

  9. 【Leetcode】查找二叉树中任意结点的最近公共祖先(LCA问题)

    寻找最近公共祖先,示例如下: 1 /           \ 2           3 /    \        /    \ 4    5      6    7 /    \          ...

随机推荐

  1. 一起来读Netty In Action(一)

    Netty是一款异步事件驱动的网络应用程序框架,支持快速的开发可维护的高性能的面向协议的服务器和客户端.在网络编程中,阻塞.非阻塞.同步.异步经常被提到.同步(synchronous) IO和异步(a ...

  2. 深浅拷贝,原生和JQuery方法实现

    7-17: 1:e.target.parentNode.remove();成功,查询一下JS原生的remove方法 2:复习JS DOM的原生操作方法,比如innerHTML(),insertBefo ...

  3. Backbone.js 和 Nodejs 的一些共同点搞不清楚

    前端方面 我用 Backbone.js 做过前端的开发,印象里就是后端按模型对象的属性把 JSON 数据发过来,我写在模板里渲染就好了 模板加载( underscore.js ) 建立模型 渲染视图 ...

  4. 纯CSS小三角制作

    无关的知识点 ① background-clip 属性规定背景的绘制区域. 1.padding-box:从padding区域(不含padding)开始向外裁剪背景. 2.border-box:从bor ...

  5. 通过配置tomcat实现项目免部署

    对于一些比较大的项目,比如说使用了EXTJS这种重量级UI框架的项目,在部署的时候,eclipse会卡崩的,所以可以通过配置tomcat来实现免部署,直接运行即可: 首先找到tomcat的localh ...

  6. DDD实战进阶第一波(十一):开发一般业务的大健康行业直销系统(实现经销商代注册用例与登录令牌分发)

    前两篇文章主要实现了经销商代注册的仓储与领域逻辑.经销商登录的仓储与相关逻辑,这篇文章主要讲述经销商代注册的用例与经销商登录的查询功能. 一.经销商代注册用例 在经销商代注册用例中,我们需要传递经销商 ...

  7. 一个C++程序中有多个cin输入的情况

    在很多C++程序中,我们都会有几次输入的操作.这种情况下,会遇到只有第一个cin语句被执行了,而后面的cin都没有执行.这是因为cin所输入的数据都放在cin缓冲区中,当第一次使用cin后,缓冲区中已 ...

  8. BZOJ_1834_[ZJOI2010]network 网络扩容_费用流

    BZOJ_1834_[ZJOI2010]network 网络扩容_费用流 题意: 给定一张有向图,每条边都有一个容量C和一个扩容费用W.这里扩容费用是指将容量扩大1所需的费用. 求:  1.在不扩容的 ...

  9. Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构(源码可下载)

    Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构(源码可下载) 说明:Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构,我采用以下三种维度来讲解 1.  代码层面. 2.  数 ...

  10. Quartz定时调度在Web中的应用

    1.在数据库中建一个job表和job日志表 job表