一、题目
求树中两个结点的最低公共祖先,此树不是二叉树,并且没有指向父节点的指针。
二、思路
思路一:
该题首先要和面试官确定是否为二叉树,得到肯定答复后,还要确定是否为二叉搜索树,是否有父指针,或者仅仅是普通二叉树。
解法一:
树为二叉搜索树时
最低公共祖先结点的大小在两个树结点大小的中间。
原文链接: https://blog.csdn.net/jingshuigg/article/details/31048141
复杂度:由于递归调用二叉树,所以时间复杂度是O(logn),空间复杂度是O(1)
public TreeNode getLowestCommonParentBST(TreeNode root,TreeNode node1,TreeNode node2) {
while(true) {
if(root==null)
return root;
if(root.val<node1.val && root.val<node2.val)
root=root.right;// 如果p,q都在root右边,则右节点更近
else if(root.val>node1.val && root.val>node2.val)
root=root.left; 如果p,q都在root左边,则左节点更近
else
return root;// 如果p,q分别在root的左右两边,说明root是最近公共祖先
}
}
测试:
public class BinaryTreeNode {
public int value;
public BinaryTreeNode leftNode;
public BinaryTreeNode rightNode;
public BinaryTreeNode(int value){
this.value = value;
leftNode = null;
rightNode = null;
}
}
public static void main(String[] args){
BinaryTreeNode A = new BinaryTreeNode(4);
BinaryTreeNode B = new BinaryTreeNode(2);
BinaryTreeNode C = new BinaryTreeNode(6);
BinaryTreeNode D = new BinaryTreeNode(1);
BinaryTreeNode E = new BinaryTreeNode(3);
BinaryTreeNode F = new BinaryTreeNode(5);
BinaryTreeNode G = new BinaryTreeNode(7);
A.leftNode = B;
A.rightNode = C;
B.leftNode = D;
B.rightNode = E;
C.leftNode = F;
C.rightNode = G;
BinaryTreeNode res1 = getLastCommonNode( A, E, F);
BinaryTreeNode res2 = getLastCommonNode( A, D, E);
BinaryTreeNode res3 = getLastCommonNode( A, B, D);
System.out.println("The lowest common ancestor of 3 and 5 is " +res1.value);
System.out.println("The lowest common ancestor of 1 and 3 is " +res2.value);
System.out.println("The lowest common ancestor of 1 and 2 is " +res3.value);
}
打印结果
结果:
The lowest common ancestor of 3 and5 is 4
The lowest common ancestor of 1 and3 is 2
Thelowest common ancestor of 1 and 2 is 2
2.树为普通树时,需要分情况,看树的节点中有没有指向父节点的指针?
原文链接: https://blog.csdn.net/jingshuigg/article/details/31048141
为什么需要指向父节点的指针?
答:如果存在parent指针,则分别从输入的p节点和q节点指向root根节点,
其实这就是两个单链表。问题转化为求两个单链表相交的第一个公共节点
解法二:
适用于(树是普通的二叉树,没有指向父节点的指针。 或者二叉搜索树)
递归的解法如下:
后序遍历二叉树,如果遍历到的当前节点为pRoot。因为是后序遍历,所以先处理pRoot的两棵子树。假设pRoot左子树时返回left,右子树返回right
- 如果pRoot为null或pLeft ,pRight,返回pRoot
- 如果left和right都为空,说明pRoot左右子树都没有pLeft ,pRight,返回null
- 如果都不为空,说明左子树上发现过pLeft 或pRight,右子树同理,并在pRoot相遇,则返回pRoot
- 如果left和right其中一个是null,另一个不为空,直接返回不为空的那个节点即是公共祖先。
(后续遍历,递归) O(n)
样例
二叉树[8, 12, 2, null, null, 6, 4, null, null, null, null]如下图所示:
8
/ \
12 2
/ \
6 4
6. 如果输入的树节点为2和12,则输出的最低公共祖先为树节点8。
7. 如果输入的树节点为2和6,则输出的最低公共祖先为树节点2。
public static BTreeNode getLastCommonNode(BTreeNode pRoot, BTreeNode pLeft, BTreeNode pRight){
//发现目标节点则通过返回值标记该子树发现了某个目标结点
if(pRoot == null || pRoot == pLeft || pRoot == pRight){
return pRoot;
}
//查看左子树中是否有目标结点,没有为null
BTreeNode left = getLastCommonNode(pRoot.left, pLeft, pRight);
//查看右子树是否有目标节点,没有为null
BTreeNode right = getLastCommonNode(pRoot.right, pLeft, pRight);
//都不为空,说明做右子树都有目标结点,则公共祖先就是本身
if(left != null && right != null){
return pRoot;
}
//如果发现了目标节点,则继续向上标记为该目标节点
return left == null ? right : right;
}
测试:
/**
* 4
* / \
* 2 6
* / \ / \
*1 3 5 7
*
*
*/
private static void test05() {
BinaryTreeNode A = new BinaryTreeNode(4);
BinaryTreeNode B = new BinaryTreeNode(2);
BinaryTreeNode C = new BinaryTreeNode(6);
BinaryTreeNode D = new BinaryTreeNode(1);
BinaryTreeNode E = new BinaryTreeNode(3);
BinaryTreeNode F = new BinaryTreeNode(5);
BinaryTreeNode G = new BinaryTreeNode(7);
A.left = B;
A.right = C;
B.left = D;
B.right = E;
C.left = F;
C.right = G;
BinaryTreeNode res1 = getLowestCommonAncestor(A, E, F);
BinaryTreeNode res2 = getLowestCommonAncestor(A, D, E);
BinaryTreeNode res3 = getLowestCommonAncestor(A, B, D);
System.out.println("The lowest common ancestor of 3 and 5 is " + res1.value);
System.out.println("The lowest common ancestor of 1 and 3 is " + res2.value);
System.out.println("The lowest common ancestor of 1 and 2 is " + res3.value);
}
打印结果:
The lowest common ancestor of 3 and 5 is 4
The lowest common ancestor of 1 and 3 is 2
The lowest common ancestor of 1 and 2 is 2
测试2:
/**
* 8
* / \
* 12 2
* / \
* 6 4
*/
private static void test07() {
//[8, 12, 2, null, null, 6, 4, null, null, null, null]
BinaryTreeNode A = new BinaryTreeNode(8);
BinaryTreeNode B = new BinaryTreeNode(12);
BinaryTreeNode C = new BinaryTreeNode(2);
BinaryTreeNode D = new BinaryTreeNode(6);
BinaryTreeNode E = new BinaryTreeNode(4);
A.left = B;
A.right = C;
C.left = D;
C.right = E;
BinaryTreeNode res1 = getLowestCommonAncestor(A, B, C);
BinaryTreeNode res3 = getLowestCommonAncestor(A, C, D);
System.out.println("The lowest common ancestor of 12 and 2 is " + res1.value);
System.out.println("The lowest common ancestor of 2 and 6 is " + res3.value);
}
打印结果:
The lowest common ancestor of 12 and 2 is 8
The lowest common ancestor of 2 and 6 is 2
解法三:
树是普通的二叉树,且树中节点有指向父节点指针。
两个节点如果在两条路径上,那么类似于“求两个链表的第一个公共节点”的算法题
/**
* // 形状普通的树
* // 1
* // / \
* // 2 3
* // / \ /\
* // 4 5 6 7
*/
public static NewBinaryTreeNode getLowestCommonAncestor1(NewBinaryTreeNode root,NewBinaryTreeNode node1,NewBinaryTreeNode node2){
if(root == null || node1 == null || node2 == null){
return null;
}
int depth1 = findTheDepthOfTheNode(root, node1, node2);
if(depth1 == -1){
return node1.parentNode;
}
int depth2 = findTheDepthOfTheNode(root, node2, node1);
if(depth2 == -1){
return node2.parentNode;
}
//p指向较深的节点 q指向较浅的节点
NewBinaryTreeNode p = depth1 > depth2 ? node1 : node2;
NewBinaryTreeNode q = depth1 > depth2 ? node2 : node1;
int depth = Math.abs(depth1 - depth2);
while(depth > 0){
p = p.parentNode;
depth --;
}
while(p != q){
p = p.parentNode;
q = q.parentNode;
}
return p;
}
//求node1的深度,如果node1和node2在一条路径上,则返回-1,否则返回node1的深度
public static int findTheDepthOfTheNode(NewBinaryTreeNode root,NewBinaryTreeNode node1,NewBinaryTreeNode node2){
int depth = 0;
while(node1.parentNode != null){
node1 = node1.parentNode;
depth ++;
if(node1 == node2){
return -1;
}
}
return depth;
}
//含有指向父节点指针的树节点
public static class NewBinaryTreeNode {
public int value;
public NewBinaryTreeNode parentNode;
public NewBinaryTreeNode leftNode;
public NewBinaryTreeNode rightNode;
public NewBinaryTreeNode(int value){
this.value = value;
parentNode = null;
leftNode = null;
rightNode = null;
}
}
测试:
/**
* // 形状普通的树
* // 1
* // / \
* // 2 3
* // / \ /\
* // 4 5 6 7
*/
private static void test04() {
NewBinaryTreeNode A = new NewBinaryTreeNode(1);
NewBinaryTreeNode B = new NewBinaryTreeNode(2);
NewBinaryTreeNode C = new NewBinaryTreeNode(3);
NewBinaryTreeNode D = new NewBinaryTreeNode(4);
NewBinaryTreeNode E = new NewBinaryTreeNode(5);
NewBinaryTreeNode F = new NewBinaryTreeNode(6);
NewBinaryTreeNode G = new NewBinaryTreeNode(7);
A.leftNode = B;
A.rightNode = C;
B.leftNode = D;
B.rightNode = E;
B.parentNode = A;
C.leftNode = F;
C.rightNode = G;
C.parentNode = A;
D.parentNode = B;
E.parentNode = B;
F.parentNode = C;
G.parentNode = C;
NewBinaryTreeNode res1 = getLowestCommonAncestor1(A,E,F);
NewBinaryTreeNode res2 = getLowestCommonAncestor1(A,D,E);
NewBinaryTreeNode res3 = getLowestCommonAncestor1(A,B,D);
NewBinaryTreeNode res4 = getLowestCommonAncestor1(A,B,F);
System.out.println("The lowest common ancestor of 5 and 6 is " +res1.value);
System.out.println("The lowest common ancestor of 4 and 5 is " +res2.value);
System.out.println("The lowest common ancestor of 2 and 4 is " +res3.value);
System.out.println("The lowest common ancestor of 2 and 6 is " +res4.value);
}
打印结果:
The lowest common ancestor of 5 and 6 is 1
The lowest common ancestor of 4 and 5 is 2
The lowest common ancestor of 2 and 4 is 2
The lowest common ancestor of 2 and 6 is 1
解法四
此树不是二叉树,并且没有指向父节点的指针。
迭代解法如下:
需要我们保存下由root根节点到p和q节点的路径,并且将路径存入list中,则问题转化为求两个list集合的最后一个共同元素。
比如我们用前序遍历的方法来得到从根结点到H 的路径的过程是这样的:( 1 )遍历到A,把A 存放到路径中去,路径中只有一个结点A; ( 2 )遍历到B,把B 存到路径中去,此时路径为A->B; ( 3 )遍历到D,把D 存放到路径中去,此,时路径为A->B->D; ( 4 ) 遍历到F,把F 存放到路径中去,此时路径为A->B->D->F;( 5) F 已经没有子结点了,因此这条路径不可能到这结点H. 把F 从路径中删除,变成A->B->D; ( 6 )遍历G. 和结点F 一样,这条路径也不能到达H. 边历完G 之后,路径仍然是A->B->D; ( 7 )由于D 的所有子结点都遍历过了,不可能到这结点H,因此D 不在从A 到H 的路径中,把D 从路径中删除,变成A->B; ( 8 )遥历E,把E 加入到路径中,此时路径变成A->B->E, ( 9 )遍历H,已经到达目标给点, A->B->E 就是从根结点开始到达H 必须经过的路径。
同样,我们也可以得到从根结点开始到达F 必须经过的路径是A->B。接着,我们求出这两个路径的最后公共结点,也就是B. B这个结点也是F 和H 的最低公共祖先.
为了得到从根结点开始到输入的两个结点的两条路径,需要遍历两次树,每边历一次的时间复杂度是O(n)。
public class Demo1 {
/*
* 获取两个节点的最低公共祖先
*/
public static TreeNode getLastCommonParent(TreeNode root, TreeNode p1, TreeNode p2) {
//path1和path2分别存储根节点到p1和p2的路径(不包括p1和p2)
List<TreeNode> path1 = new ArrayList<TreeNode>();
List<TreeNode> path2 = new ArrayList<TreeNode>();
List<TreeNode> tmpList = new ArrayList<TreeNode>();
getNodePath(root, p1, tmpList, path1);
getNodePath(root, p2, tmpList, path2);
//如果路径不存在,返回空
if (path1.size() == 0 || path2.size() == 0)
return null;
return getLastCommonParent(path1, path2);
}
// 获取根节点到目标节点的路径
public static void getNodePath(TreeNode root, TreeNode target, List<TreeNode> tmpList, List<TreeNode> path) {
//鲁棒性
if (root == null || root == target)
return;
tmpList.add(root);
List<TreeNode> children = root.children;
for (TreeNode node : children) {
if (node == target) {
path.addAll(tmpList);
path.add(node);
break;//只是跳出循环,返回上一个递归
}
getNodePath(node, target, tmpList, path);
}
tmpList.remove(tmpList.size() - 1);
}
//将问题转化为求链表最后一个共同节点
private static TreeNode getLastCommonParent(List<TreeNode> p1, List<TreeNode> p2) {
TreeNode tmpNode = null;
for (int i = 0; i < p1.size(); i++) {
if (p1.get(i) != p2.get(i))
break;
tmpNode = p1.get(i);
}
return tmpNode;
}
// 节点类
private static class TreeNode {
int val;
List<TreeNode> children = new ArrayList<>();
public TreeNode(int val) {
this.val = val;
}
@Override
public String toString() {
return val + "";
}
}
public static void main(String[] args) {
test01();
System.out.println("==========");
test02();
System.out.println("==========");
test03();
}
// 形状普通的树
// 1
// / \
// 2 3
// / \
// 4 5
// / \ / | \
// 6 7 8 9 10
public static void test01() {
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
TreeNode n6 = new TreeNode(6);
TreeNode n7 = new TreeNode(7);
TreeNode n8 = new TreeNode(8);
TreeNode n9 = new TreeNode(9);
TreeNode n10 = new TreeNode(10);
n1.children.add(n2);
n1.children.add(n3);
n2.children.add(n4);
n4.children.add(n6);
n4.children.add(n7);
n3.children.add(n5);
n5.children.add(n8);
n5.children.add(n9);
n5.children.add(n10);
System.out.println(getLastCommonParent(n1, n9, n10));
System.out.println("getLastCommonParent1 test01 n4:n6::" + getLastCommonParent1(n1, n4, n6));//
}
打印结果:
getLastCommonParent1 test01::5
getLastCommonParent1 test01 n4:n6::4
// 树退化成一个链表
// 1
// /
// 2
// /
// 3
// /
// 4
// /
// 5
private static void test02() {
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
n1.children.add(n2);
n2.children.add(n3);
n3.children.add(n4);
n4.children.add(n5);
System.out.println(getLastCommonParent(n1, n4, n5));
}
打印结果:
getLastCommonParent1 test02::4
// 树退化成一个链表,一个结点不在树中
// 1
// /
// 2
// /
// 3
// /
// 4
// /
// 5
private static void test03() {
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
TreeNode n6 = new TreeNode(6);
n1.children.add(n2);
n2.children.add(n3);
n3.children.add(n4);
n4.children.add(n5);
System.out.println(getLastCommonParent(n1, n5, n6));
}
}
打印结果:
getLastCommonParent1 test03::null