【要求】:找出二叉树上任意两个节点的最近共同父结点。
这道题的简化版本就是将“二叉树”换成“搜索二叉树”,那么可以通过节点的值的比较利用递归来判断。这里,由于是指任意二叉树,任意节点,所以,只能通过搜索来查找。LCA问题的解法非常多,这里介绍的只是一部分。
1 通过各自的路径求解
由于题目是求得两个节点的最低的公共祖先,所以可以先求得从根到两个目标节点的路径,然后比较路径求解。我这里直接贴源代码,如果想了解求路径的详细知识,可以参考博主的上一篇博文:【二叉树系列3: 二叉树的路径问题】
TreeNode.java
package BinaryTree;
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(TreeNode l, TreeNode r, int v) {
left = l;
right = r;
val = v;
}
public TreeNode(int v) {
val = v;
}
}
BinaryTreeTest.java
核心:
public TreeNode lowestCommonAncestor_byPath(TreeNode t, int a, int b) {
if(t == null){
System.err.println("The tree is null!");
return null;
}
if(a == b){
System.err.println("Two nodes are the same!");
return null;
}
ArrayList<TreeNode> aPath, bPath;
aPath = printPath(t, a);
bPath = printPath(t, b);
if(aPath == null || aPath == null){
System.err.println("Cannot find the node!");
return null;
}
TreeNode aNode, bNode, common;
common = t;
int len = Math.min(aPath.size(), bPath.size());
for(int i=0; i< len; i++){
aNode = aPath.get(i);
bNode = bPath.get(i);
if(aNode.val == bNode.val){
common = aNode;
}
else {
return common;
}
}
return t;
}
求路径的代码:
/** 包装方法 */
public ArrayList<TreeNode> printPath(TreeNode t, int val) {
ArrayList<TreeNode> path = new ArrayList<>();
return printPath(t, val, path);
}
/** 输出给定节点到根节点的路径 */
private ArrayList<TreeNode> printPath(TreeNode t, int val, ArrayList<TreeNode> path) {
if (t == null) {
return null;
}
path.add(t);
// 如果找到就直接返回
if (val == t.val) {
//System.out.println("Find!");
return path;
} else {
// 如果没有找到则搜索它的左子树
ArrayList<TreeNode> leftPath = (ArrayList<TreeNode>) path.clone();
ArrayList<TreeNode> lRet = printPath(t.left, val, leftPath);
if (lRet != null) {
// 说明在做子树中找到了这个点,则返回
path = null;
return lRet;
}
// 如果在左子树没有找到,则搜索右子树
else {
ArrayList<TreeNode> rightPath = (ArrayList<TreeNode>) path.clone();
ArrayList<TreeNode> rRet = printPath(t.right, val, rightPath);
if (rRet != null) {
path = null;
return rRet;
}
// 说明右子树也没有找到
else {
path = null;
return null;
}
}
}
}
【测试】Main.java
package BinaryTree;
public class Main {
public static void main(String[] args) {
/***
* 新建一个二叉树:
*
* 15
* / \
* 12 25
* / \
* 9 13
* ***/
TreeNode root = new TreeNode(15);
TreeNode l = new TreeNode(12);
root.left = l;
TreeNode r = new TreeNode(25);
root.right = r;
TreeNode ll = new TreeNode(9);
TreeNode lr = new TreeNode(13);
l.left = ll;
l.right = lr;
BinaryTreeTest bTreeTest = new BinaryTreeTest();
TreeNode a = bTreeTest.lowestCommonAncestor_byPath(root, 9, 13);
System.out.printf("\nThe lowest common ancestor of 9 and 13 is: %-4d ", a.val);
TreeNode b = bTreeTest.lowestCommonAncestor_byPath(root, 9, 25);
System.out.printf("\nThe lowest common ancestor of 9 and 25 is: %-4d ", b.val);
}
}
结果:
The lowest common ancestor of 9 and 13 is: 12
The lowest common ancestor of 9 and 25 is: 15
此算法的主要开销是获取根节点到指定节点的路径,总共两次,每次就是对利用先序遍历对树进行搜索,所以时间复杂度是 O(N) ,空间复杂度是O (log N)。
2 一次遍历求解
上面的方法虽然是O(N),但是操作依然繁琐了一点,并且需要额外的空间来存储路径。上面的代码很多有木有!? 我敲得都烦了。其实可以只遍历一次,利用递归的巧妙之处。学好二叉树,其实就是学好递归。
从root开始遍历,如果n1和n2中的任一个和root匹配,那么root就是LCA。 如果都不匹配,则分别递归左、右子树,如果有一个 key(n1或n2)出现在左子树,并且另一个key(n1或n2)出现在右子树,则root就是LCA. 如果两个key都出现在左子树,则说明LCA在左子树中,否则在右子树。
public TreeNode findLCA(TreeNode t, int a, int b) {
if (t == null) {
return null;
}
if (t.val == a || t.val == b) {
return t;
}
TreeNode leftResult = findLCA(t.left, a, b);
TreeNode rightResult = findLCA(t.right, a, b);
// 如果在左子树找到一个值,在右子树也找到一个值,毫无疑问,LCA就是root
if (leftResult != null && rightResult != null) {
return t;
}
// 如果左子树找到了一个值,但是右子树一个都没有找到,这说明两个节点都在左子树中
// 由于我们采用的是中序遍历,每次进行判断的都是目前的root,所以左子树的返回值就是LAC
else if (leftResult != null && rightResult == null) {
return leftResult;
}
// 两个节点全在右子树中
else if (leftResult == null && rightResult != null) {
return rightResult;
}
// 如果左右子树都没有搜索到,则节点不在树种
else {
return null;
}
}
测试:
package BinaryTree;
public class Main {
public static void main(String[] args) {
/***
* 新建一个二叉树:
*
* 15
* / \
* 12 25
* / \
* 9 13
* ***/
TreeNode root = new TreeNode(15);
TreeNode l = new TreeNode(12);
root.left = l;
TreeNode r = new TreeNode(25);
root.right = r;
TreeNode ll = new TreeNode(9);
TreeNode lr = new TreeNode(13);
l.left = ll;
l.right = lr;
BinaryTreeTest bTreeTest = new BinaryTreeTest();
TreeNode a = bTreeTest.findLCA(root, 9, 13);
System.out.printf("\nThe lowest common ancestor of 9 and 13 is: %-4d ", a.val);
TreeNode b = bTreeTest.findLCA(root, 9, 25);
System.out.printf("\nThe lowest common ancestor of 9 and 25 is: %-4d ", b.val);
TreeNode c = bTreeTest.findLCA(root, 9, 33);
try {
System.out.printf("\nThe lowest common ancestor of 9 and 33 is: %-4d ", c.val);
} catch (Exception e) {
System.out.println("\nThe lowest common ancestor of 9 and 33 is:" + e.toString());
}
TreeNode d = bTreeTest.findLCA(root, 0, 44);
try {
System.out.printf("\nThe lowest common ancestor of 0 and 44 is: %-4d ", d.val);
} catch (Exception e) {
System.out.println("\nThe lowest common ancestor of 0 and 44 is:" + e.toString());
}
}
}
结果:
The lowest common ancestor of 9 and 13 is: 12
The lowest common ancestor of 9 and 25 is: 15
The lowest common ancestor of 9 and 33 is: 9
The lowest common ancestor of 0 and 44 is:java.lang.NullPointerException
结果是有问题的。虽然此算法可以反映出两个节点都不在树中的议程情况,但是没有考虑【一个节点在树中,一个不在】的异常。造成这样的错误的原因是,在一个子树的搜索结果为空,另一个不为空的情况时,我们其实只确认了一个节点的存在性,另一个没有去验证。所以,这个算法如果在不需要考虑节点不存在的情况下很好用。