剑指Offer(第二版)面试题68:树中两个节点的最低公共祖先节点

题目:

输入两个树节点,求它们的最低公共祖先节点。

一、二叉搜索树

应聘者:这棵树是不是二叉树?

面试官:是二叉树,并且是二叉搜索树。

思路:

二叉搜索树是经过排序的,位于左子树的节点都比父节点小,位于右子树的节点都比父节点大。既然要找最低的公共祖先节点,我们可以从根节点开始进行比较。

若当前节点的值比两个节点的值都大,那么最低的祖先节点一定在当前节点的左子树中,则遍历当前节点的左子节点;

反之,若当前节点的值比两个节点的值都小,那么最低的祖先节点一定在当前节点的右子树中,则遍历当前节点的右子节点;

这样,直到找到一个节点,位于两个节点值的中间,则找到了最低的公共祖先节点。

struct TreeNode
{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr){}
};

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
    if (pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr)
        return nullptr;
    if (pRoot->val > pNode1->val && pRoot->val > pNode2->val)
        return GetLastCommonParent(pRoot->left, pNode1, pNode2);
    else if (pRoot->val < pNode1->val && pRoot->val < pNode2->val)
        return GetLastCommonParent(pRoot->right, pNode1, pNode2);
    else
        return pRoot;

}

二、普通的二叉树,树的节点中有指向父节点的指针

面试官:如果只是普通的二叉树呢?

应聘者:树的节点中有没有指向父节点的指针?

面试官:为什么需要指向父节点的指针?

应聘者:如果存在parent指针,则分别从输入的p节点和q节点指向root根节点,其实这就是两个单链表。问题转化为求两个单链表相交的第一个公共节点。

(此题可以用《剑指offer》中面试题52:两个链表的第一个公共节点,求解)

三、普通的二叉树,树的节点中没有指向父节点的指针

面试官:那如果不存在parent指针呢?

应聘者:所谓两个节点的公共祖先,指的是这两个节点都出现在某个节点的子树中。我们可以从根节点开始遍历一棵树,每遍历到一个节点时,判断两个输入节点是不是在它的子树中。如果在子树中,则分别遍历它的所有子节点,并判断两个节点是不是在它们的子树中。这样从上到下一直找到的第一个节点,它自己的子树中同时包含两个输入的节点而它的子节点却没有,那么该节点就是最低的公共祖先。

struct TreeNode
{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr){}
};

bool FindTreeNode(TreeNode* pRoot, TreeNode* pNode)
{
    if (pRoot == nullptr || pNode == nullptr)
        return false;
    if (pRoot == pNode)
        return true;
    bool found = FindTreeNode(pRoot->left, pNode);
    if (!found)
        return FindTreeNode(pRoot->right, pNode);
    return found;
}

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
    if (pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr)
        return nullptr;
    if (FindTreeNode(pRoot->left, pNode1))
    {
        if (FindTreeNode(pRoot->right, pNode2))
            return pRoot;
        else
            return GetLastCommonParent(pRoot->left, pNode1, pNode2);
    }
    else
    {
        if (FindTreeNode(pRoot->left, pNode2))
            return pRoot;
        else
            return GetLastCommonParent(pRoot->right, pNode1, pNode2);
    }
}

面试官:听起来不错。很明显,这种思路会对同一节点重复遍历很多次。你想想看还有没有更快的算法?

应聘者:我的想法是用两个链表分别保存从跟节点到输入的两个节点的路径,然后把问题转换成两个链表的最后公共节点。

struct TreeNode
{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr){}
};

bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, vector<TreeNode* > &path)
{
    if (pRoot == nullptr)
        return false;
    path.push_back(pRoot);
    if (pRoot == pNode)
        return true;

    bool found = false;
    found = GetNodePath(pRoot->left, pNode, path);

    if (!found)
        found = GetNodePath(pRoot->right, pNode, path);

    if (!found)
        path.pop_back();

    return found;
}

TreeNode* GetLastCommonNode(const vector<TreeNode* > &path1, const vector<TreeNode* > &path2)
{
    vector<TreeNode* >::const_iterator ite1 = path1.begin();
    vector<TreeNode* >::const_iterator ite2 = path2.begin();

    TreeNode* pLast = nullptr;
    while (ite1 != path1.end() && ite2 != path2.end())
    {
        if (*ite1 == *ite2)
            pLast = *ite1;
        else
            break;
        ++ite1;
        ++ite2;
    }
    return pLast;
}

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
    if (pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr)
        return nullptr;
    vector<TreeNode* > path1;
    vector<TreeNode* > path2;
    GetNodePath(pRoot, pNode1, path1);
    GetNodePath(pRoot, pNode2, path2);

    return GetLastCommonNode(path1, path2);
}

四、普通的树

(不知道树有多少个子树的情况)

struct TreeNode
{
    int val;
    vector<TreeNode* > children;//由于是普通树,不知有多少个子树,所以用vector保存子树
    TreeNode(int x) : val(x){}
};

bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, vector<TreeNode* > &path)
{
    if(pRoot == nullptr)
        return false;
    path.push_back(pRoot);
    if (pRoot == pNode)
        return true;
    bool found = false;
    vector<TreeNode* >::iterator ite = pRoot->children.begin();
    while (!found && ite < pRoot->children.end())
    {
        found = GetNodePath(*ite, pNode, path);
        ++ite;
    }
    if (!found)
        path.pop_back();
    return found;
}

TreeNode* GetLastCommonNode(const vector<TreeNode* > &path1, const vector<TreeNode* > &path2)
{
    vector<TreeNode* >::const_iterator ite1 = path1.begin();
    vector<TreeNode* >::const_iterator ite2 = path2.begin();

    TreeNode* pLast = nullptr;
    while (ite1 != path1.end() && ite2 != path2.end())
    {
        if (*ite1 == *ite2)
            pLast = *ite1;
        else
            break;
        ++ite1;
        ++ite2;
    }
    return pLast;
}

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2)
{
    if (pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr)
        return nullptr;
    vector<TreeNode* > path1;
    vector<TreeNode* > path2;
    GetNodePath(pRoot, pNode1, path1);  
    GetNodePath(pRoot, pNode2, path2);

    return GetLastCommonNode(path1, path2);
}

猜你喜欢

转载自blog.csdn.net/chen134225/article/details/81411573