算法总是美的,充满着magic.
一.前言
给定一棵树,同时给出树中的两个结点或者两个以上节点,求它们的最低公共祖先。这就是常见的LCA(Lowest Common Ancestor )问题。
二.两个节点的LCA问题
常规解法
1.1 思路
下面是一个简单的复杂度为 O(n) 的算法,解决LCA问题
1) 找到从根到n1的路径,并存储在一个向量或数组中。
2)找到从根到n2的路径,并存储在一个向量或数组中。
3) 遍历这两条路径,直到遇到一个不同的节点,则前面的那个即为最低公共祖先.
1.2 代码
//遍历两次,时间复杂度为O(n)
private static int findLCA(BinaryTree root, int key1, int key2) {
if (root == null) {
return -1;
}
List<Integer> path1 = new ArrayList<>();
List<Integer> path2 = new ArrayList<>();
boolean found1 = findPath(root,path1,key1);
boolean found2 = findPath(root,path2,key2);
int ans = 0;
if (found1 && found2) {
for (int i = 0; i < path1.size(); i++) {
if (path1.get(i) != path2.get(i)) {
break;
}else {
ans = path1.get(i);
}
}
return ans;
}
return -1;
}
/** * @category 从根节点找到一条到指点数的路径. * @param root 二叉树根节点 * @param path 存储根节点到key的路径 * @param key 要查找的数 * @return 若路径存在则返回true,否则,false. * */
private static boolean findPath(BinaryTree root, List<Integer> path, int key) {
if (root == null) {
return false;
}
path.add(root.value);
if (root.value == key) {
return true;
}
boolean find = (findPath(root.left, path, key)||findPath(root.right, path, key));
if (find) {
return true;
}
path.remove(path.indexOf(root.value));
return false;
}
优化解法
只遍历一次
1.1 思路
从root开始遍历,如果n1和n2中的任一个和root匹配,那么root就是LCA。 如果都不匹配,则分别递归左、右子树,如果有一个 key(n1或n2)出现在左子树,并且另一个key(n1或n2)出现在右子树,则root就是LCA. 如果两个key都出现在左子树,则说明LCA在左子树中,否则在右子树。
1.2 代码
/** * 从root节点向下遍历,若root.value等于任一要查找的值,则root为祖先节点, * 如果都不匹配,则遍历左子树和右子树,如果一个key出现在左子树,一个出现在右子树,则root为祖先节点, * 如果两个都出现在左子树,则说明LCA在左子树,否则在右子树. * @param root 二叉树根节点 * @param key1,key2 要查找的数 * @return 公共祖先节点 */
private static BinaryTree findLCA(BinaryTree root, int key1, int key2) {
if (root == null) {
return null;
}
if (root.value == key1 || root.value == key2) {
return root;
}
BinaryTree left = findLCA(root.left, key1, key2);
BinaryTree right = findLCA(root.right, key1, key2);
if (left != null && right != null) {
return root;
}
return (left !=null)?left:right;
}
三.三个节点的LCA问题
题目如题.
1.1 思路
思路同上述常规解法. 相当找三个链表的最后一个公共节点.
1.2 代码
/** * @param root 二叉树根节点 * @param key1,key2,key3 要查找的数 * @return 公共祖先节点 */
private static int findLCA(BinaryTree root, int key1, int key2, int key3) {
if (root == null) {
return -1;
}
List<Integer> path1 = new ArrayList<>();
List<Integer> path2 = new ArrayList<>();
List<Integer> path3 = new ArrayList<>();
boolean found1 = findPath(root,path1,key1);
boolean found2 = findPath(root,path2,key2);
boolean found3 = findPath(root,path3,key3);
int ans = 0;
if (found1 && found2 && found3) {
int index = 0;
while(index<path1.size()){
if (path1.get(index)==path2.get(index)&&path1.get(index)==path3.get(index)) {
ans = path1.get(index);
index++;
}else {
break;
}
}
return ans;
}
return -1;
}
private static boolean findPath(BinaryTree root, List<Integer> path, int key) {
if (root == null) {
return false;
}
path.add(root.value);
if (root.value == key) {
return true;
}
boolean found = (findPath(root.left, path, key)||findPath(root.right, path, key));
if (found) {
return true;
}
path.remove(path.indexOf(root.value));
return false;
}
四.三个节点的LCA问题
给定一满二叉排序树,节点从1-(2^k-1),给出三个数a,b,c;找出它们的最低公共祖先. (腾讯题)
直接上代码:
import java.util.Scanner;
/** * LCA 问题: 满二叉排序树中三个节点的最低公共祖先 * @author dingding * @version 9-13 */
public class BT_NoParentPtr_Solution4 {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
while(cin.hasNext()){
int k = cin.nextInt();
int a = cin.nextInt();
int b = cin.nextInt();
int c = cin.nextInt();
int result = findLCA(a,b,c,1,(2<<k)-1);
System.out.println(result);
}
}
/** * 若根节点的值为m,若存在(a-m)(b-m)<0 || (a-m)(c-m)<0 || (b-m)(c-m)<0,则节点m 为最低祖先. * @param a,b,c 查找的三个数 * @param left 数组最左边的值,right 数组最右边. (数组为二叉排序数组) * @return 公共祖先节点 */
private static int findLCA(int a, int b, int c, int left, int right) {
int m = left+(right-left)/2;
if (((a-m)*(b-m)<=0) || ((a-m)*(c-m)<=0) || ((b-m)*(c-m)<=0)) {
return m;
}else if(a>m){ //不在根节点,a>m必有一节点在右子树,搜索右侧.
return findLCA(a, b, c, m+1, right);
}else {
return findLCA(a, b, c, left, m-1);
}
}
}