题目:树中两个节点的最低公共祖先
输入一棵树的根节点,输入两个被观察节点,求这两个节点的最低(最近)公共祖先。
思路:
此题比较开放,主要是对于“树”没有做明确说明,所以原书中就对树的可能情况做了假设,然后就衍生出多种思路。
1.如果是二叉搜索树:
遍历找到比第一个节点大,比第二个节点小的节点即可。
以下是java参考代码:
//特殊情况一:当树是一颗二叉搜索树
public static TreeNode lowparent(TreeNode root,TreeNode root1,TreeNode root2){
if(root==null||root1==null||root2==null) return null;
if(root1.val==root.val||root2.val==root.val) return root;//其中一个节点为根节点
if(root1.val<root.val){
if(root2.val>root.val) return root;
else
return lowparent(root.left,root1,root2);
}else{
if(root2.val<root.val) return root;
return lowparent(root.right,root1,root2);
}
}
2.如果是父子间有双向指针的树:
由下往上看,转化为找两个链表的第一个公共节点问题
/*
*分别遍历两个链表,得到分别对应的长度。然后求长度的差值,把较长的那个链表向后移动这个差值的个数,
*然后一一比较即可。
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
int lenA = getLength(headA), lenB = getLength(headB);
if (lenA > lenB) {//让长的链表先走多出的长度
for (int i = 0; i < lenA - lenB; ++i) headA = headA.next;
} else {
for (int i = 0; i < lenB - lenA; ++i) headB = headB.next;
}
while (headA != null && headB != null && headA != headB) {//需要判断两个节点都非空。
headA = headA.next;
headB = headB.next;
}
return (headA != null && headB != null) ? headA : null;
}
public int getLength(ListNode head) {//求链表的长度
int cnt = 0;
while (head != null) {
++cnt;
head = head.next;
}
return cnt;
}
}
/*
*用环的思想来做,我们让两条链表分别从各自的开头开始往后遍历,当其中一条遍历到末尾时,
*我们跳到另一个条链表的开头继续遍历。
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode a = headA, b = headB;
while (a != b) {
a = (a != null) ? a.next : headB;
b = (b != null) ? b.next : headA;
}
return a;
}
}
3.如果只是一个包含父到子的指针的普通树:
3.1)如果不能使用额外空间,从根节点开始判断他的子树是否包含那两个节点,找到最小的的子树即可
时间复杂度o(n^2)(此为最差,平均不太好算。。。),空间复杂度为o(1)
3.2) 如果能用额外空间,可以遍历两次(深度优先)获取根节点到那两个节点的路径,然后求两个路径的最后一个公共节点
时间复杂度o(n),空间复杂度o(logn)。
下面仅对(3),以下图所示的树为例,进行思路实现与求解验证
A
/ \
B C
/ \
D E
/ \ / | \
F G H I J
基于以上思路,java参考代码如下:
第一种解法的参考代码:
TreeNode getLastCommonParent(TreeNode root,TreeNode t1,TreeNode t2){
if(findNode(root.left,t1)){//t1在左
if(findNode(root.right,t2)){//t2在右,则直接输出根节点
return root;
}else{
return getLastCommonParent(root.left,t1,t2);//t1和t2都在左边,在根节点的左子树中找
}
}else{
if(findNode(root.left,t2)){
return root;
}else{
return getLastCommonParent(root.right,t1,t2)
}
}
}
// 查找节点node是否在当前 二叉树中
boolean findNode(TreeNode root,TreeNode node){
if(root == null || node == null){
return false;
}
if(root == node){
return true;
}
boolean found = findNode(root.left,node);
if(!found){
found = findNode(root.right,node);
}
return found;//最后需要返回found值,判断是否存在node
}
参考处面试中的二叉树问题集
import java.util.*;
public class CommonParentInTree {
public static class CommonTreeNode{//定义一棵普通的树
public char val;
public List<CommonTreeNode> children;
public CommonTreeNode(char val){
this.val = val;
children = new LinkedList<>();
}
public void addChildren(CommonTreeNode... children){//java中多参数的传递方法...
for(CommonTreeNode child:children)
this.children.add(child);
}
}
// 3.1所述的解法
public static CommonTreeNode getLastParent1(CommonTreeNode root,CommonTreeNode node1,CommonTreeNode node2){
if(root==null || node1==null || node2==null || !isInSubTree(root,node1,node2))
return null;
CommonTreeNode curNode = root;
while (true){
for(CommonTreeNode child:curNode.children){//遍历当前节点的所有子树
if(isInSubTree(child,node1,node2)){
curNode = child;
break;
}
if(child==curNode.children.get(curNode.children.size()-1))
return curNode;
}
}
}
public static boolean isInSubTree(CommonTreeNode root,CommonTreeNode node1,CommonTreeNode node2){
Queue<CommonTreeNode> queue = new LinkedList<>();
CommonTreeNode temp = null;
int count = 0;
queue.add(root);
while (count!=2 && !queue.isEmpty()){
temp = queue.poll();
if(temp==node1||temp==node2)
count++;
if(!temp.children.isEmpty())
queue.addAll(temp.children);
}
if(count==2)
return true;
return false;
}
// 3.2所述的解法
public static CommonTreeNode getLastParent2(CommonTreeNode root,CommonTreeNode node1,CommonTreeNode node2){
List<CommonTreeNode> path1 = new ArrayList<>();
List<CommonTreeNode> path2 = new ArrayList<>();
getPath(root,node1,path1);
getPath(root,node2,path2);
CommonTreeNode lastParent = null;
for(int i=0;i<path1.size()&&i<path2.size();i++){
if(path1.get(i)==path2.get(i))
lastParent = path1.get(i);
else
break;
}
return lastParent;
}
public static boolean getPath(CommonTreeNode root,CommonTreeNode node,List<CommonTreeNode> curPath){
if(root==node)
return true;
curPath.add(root);
for(CommonTreeNode child:root.children){
if(getPath(child,node,curPath))
return true;
}
curPath.remove(curPath.size()-1);
return false;
}
public static void main(String[] args){
CommonTreeNode root = new CommonTreeNode('A');
CommonTreeNode b = new CommonTreeNode('B');
CommonTreeNode c = new CommonTreeNode('C');
CommonTreeNode d = new CommonTreeNode('D');
CommonTreeNode e = new CommonTreeNode('E');
CommonTreeNode f = new CommonTreeNode('F');
CommonTreeNode g = new CommonTreeNode('G');
CommonTreeNode h = new CommonTreeNode('H');
CommonTreeNode i = new CommonTreeNode('I');
CommonTreeNode j = new CommonTreeNode('J');
root.addChildren(b,c);
b.addChildren(d,e);
d.addChildren(f,g);
e.addChildren(h,i,j);
System.out.println(getLastParent1(root,f,h).val);
System.out.println(getLastParent2(root,f,h).val);
System.out.println(getLastParent1(root,h,i).val);
System.out.println(getLastParent2(root,h,i).val);
}
}
测试用例:
a.功能测试(普通形态的树;形状退化成链状的树)。
b.特殊输入测试(指向树根节点的指针为null指针)。