《剑指Offer》面试题:树中两结点最低公共祖先

本文介绍了一种求解树中两个结点最低公共祖先的方法,通过前序和后序遍历获取结点路径,并找出共同祖先。适用于二叉树及普通树。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

给定一棵树,同时给出树中的两个结点,求它们的最低公共祖先。

思路
这类的题目,方法蛮多的,思路也不难理解,基本都是各种遍历的变种,主要是写代码,尤其基于递归的代码。
首先如果是二叉排序树自然不用说了,判断的一句就是该节点的值是否位于输入的这两个节点之间,可以用前序遍历来做。
如果是普通的树或者二叉树,解题思路是一样的,可以考虑前序遍历,得到两个路径,用链表或数组保存起来,然后找出两条路径的最后一个公共节点即可。
也可以后序遍历的方式,遍历到输入的节点时,将该节点及其后面遍历到的节点都保存到一个链表或数组中,然后找出两条路径的第一个公共机节点即可。

下面利用前序遍历和后续遍历寻找节点在树中的路径,都进行了编码实现。

/*

输入:
输入可能包含多个测试样例。
对于每个测试案例,输入的第一行为一个数n(0<n<1000),代表测试样例的个数。
其中每个测试样例包括两行,第一行为一个二叉树的先序遍历序列,其中左右子树若为空则用0代替,其中二叉树的结点个数node_num<10000。
第二行为树中的两个结点的值m1与m2(0<m1,m2<10000)。
输出:
对应每个测试案例,
输出给定的树中两个结点的最低公共祖先结点的值,若两个给定结点无最低公共祖先,则输出“My God”。
样例输入:
2
1 2 4 6 0 0 7 0 0 5 8 0 0 9 0 0 3 0 0
6 8
1 2 4 6 0 0 7 0 0 5 8 0 0 9 0 0 3 0 0
6 12
样例输出:
2
My God
*/


#include<stdio.h>
#include<stdlib.h>
struct TreeNode{
    int mValue;
    struct TreeNode *lchild;
    struct TreeNode *rchild;
};


void createTree(TreeNode **ppNode){
    int val;
    scanf("%d",&val);
    if(val==0){
        *ppNode=NULL;
    }
    else{
        //为新结点开辟空间。
        (*ppNode)=(TreeNode *)malloc(sizeof(struct TreeNode));
        if((*ppNode)==NULL){
            exit(EXIT_FAILURE);
        } 
        (*ppNode)->mValue=val;
        (*ppNode)->lchild=NULL;
        (*ppNode)->rchild=NULL;
        createTree(&((*ppNode)->lchild));
        createTree(&((*ppNode)->rchild));

    }
} 
//在树中找到data的路径并保存在path1数组中。
/*
函数参数的说明
@param pRoot:节点的指针
@param data:待查找的结点的value值
@param index:数组的下标索引。即该节点将存储在数组的位置
@param len:用来保存最终数组中存储的节点数目。 
*/ 
bool getNodePath(TreeNode *pRoot,int *path,int data,int index,int *len){
    if(pRoot==NULL){
        return false;
    } 
    //利用先序遍历来寻找data存在节点的路径
    //第一步:当前结点保存在数组中
    path[index]=pRoot->mValue; 
    //第二步:检查当前结点是否是我们需找的节点。
    if(data==path[index]){
        *len=index+1;
        return true;
    }

    else{
        bool isExist=getNodePath(pRoot->lchild,path, data,index+1,len);
        if(!isExist){
            isExist=getNodePath(pRoot->rchild,path,data,index+1,len);
        }
        return isExist;
    }


} 
//返回的是第一个公共节点在数组中的下标。,由于公共节点在两个数组前几个相同的元素都为公共节点,因此每个公共节点在在两个数组中的下标是相同的。 
int findPublicNode(int *path1,int *path2,int len1,int len2){
    if(path1==NULL||path2==NULL||len1<1||len2<1){
        return -1;
    }
    //以下思路与求链表的第一个公共节点比较类似
    //第一步:将找出大树组、小数组
    int *lengthPath=path1;
    int *shortPath=path2;
    int longthLen=len1;
    int shortLen=len2;
    if(len1<len2){
        lengthPath=path2;
        shortPath=path1;
        longthLen=len2;
        shortLen=len1;
    }
    //从后往前找,当然也可以从前往后找,一样。 
    for(int i=shortLen-1;i>=0;i--){
        if(lengthPath[i]==shortPath[i]){
            return i;
        }
    }
    return -1; 

} 
void destoryTree(TreeNode **pRoot){
    if(*pRoot==NULL)
        return;
    //按照后序遍历来进行销毁
    destoryTree(&((*pRoot)->lchild));
    destoryTree(&((*pRoot)->rchild));
    if((*pRoot)->lchild==NULL&&(*pRoot)->rchild==NULL){
        free(*pRoot);
        (*pRoot)=NULL;
    }

}
int main(void){
    //借助两个数组来保存遍历的路径。 

    int path1[10000];
    int path2[10000];
    int n;//测试用例的个数 
    while(scanf("%d",&n)!=EOF&&n>0){
        for(int i=0;i<n;i++){
            TreeNode *pRoot;
            //根据输入来创建一个树。 
            createTree(&pRoot);
            //接收待查找的节点
            int first,second;
            scanf("%d%d",&first,&second);
            //寻找第一个公共结点
            int len1=0;
            int len2=0;
            bool isExist1=getNodePath(pRoot,path1,first,0,&len1); 
            bool isExist2=getNodePath(pRoot,path2,second,0,&len2); 
            if(!(isExist1&&isExist2)){
                printf("My God\n");
            }
            else{
                //在两个路径数组中找出公共结点的value值。 
                int publicNodeIndex=findPublicNode(path1,path2,len1,len2);
                if(publicNodeIndex!=-1){
                    printf("%d\n",path1[publicNodeIndex]);
                }
                else{
                    printf("My God\n");
                }

            }

            //最后销毁树的内存空间
            destoryTree(&pRoot); 

        } 

    }
    return 0;
}

运行截图如下:

上面是利用先序遍历来寻找节点在树中的路径,下面是利用后序遍历来做,这里*len的是最大的下标。

// 
bool getNodePath(TreeNode *pRoot,int *path,int data,int *len){
    if(pRoot==NULL){
        return false;
    } 
    bool isExist=getNodePath(pRoot->lchild,path, data,len);
    if(!isExist){
        isExist=getNodePath(pRoot->rchild,path, data,len);

    }

    if(isExist){//如果其的子节点存在路径中,则此节点也肯定存在路径中。 
        path[++(*len)]=pRoot->mValue;
    }

    if(pRoot->mValue==data){
        *len=0;
        path[*len]=pRoot->mValue;
        return true;
    }


    return isExist;

} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值