程序员面试金典——4.7最近公共祖先

本文探讨了在不同类型的二叉树中寻找两个指定节点的最近公共祖先的有效算法。包括利用数学推理、二叉排序树特性、单向链表公共节点、普通二叉树等方法。

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

Solution1:我的答案

【针对阉割版的题目的偷鸡摸狗的做法】注意:书中的原题要更复杂一些,在牛客网中二叉树被设置为满二叉树且结点值从1开始,依次加1。

class LCA {
public:
    int getLCA(int a, int b) { //看来是一个数学推理题。。。
        // write code here
        if (a == b)
            return a;
        int A = a, B = b;
        while (A != B) {
            if (A > B)
                A /= 2;
            else if (A < B)
                B /= 2;
        }
        return A;
    }
};

Solution2:正经做法

问题:找出二叉树中某两个结点的第一个公共祖先。
参考网址:
[1]https://blog.youkuaiyun.com/qinzhenhua100/article/details/64528136
[2]https://blog.youkuaiyun.com/xyzbaihaiping/article/details/52122885
题目分析:

假设1:这个二叉树是二叉排序树(O(N))

如果题目中的二叉树是二叉排序树,那么这道题目变的相对简单很多,我们只需要从根节点开始遍历,有以下三种情况发生:
(1)如果题目给的两个节点的值都大于当前节点,那么继续遍历当前节点的右子树
(2)如果题目给的两个节点的值都小于当前节点,那么继续遍历当前节点的左子树
(3)如果当前节点大于其中一个节点而小于其中另外一个节点,那么则返回该节点,该节点便是两个节点的公共祖先节点。

假设2:这个二叉树是普通的二叉树,但是存在指向父节点的指针

由于每个节点存在指向父节点的指针,所以如果给定任意一个节点,便可以找到从该节点到根节点的路径,因此我们可以找到题目中给予的两个节点分别到根节点的路径,因此该题目便变成了求两个单向链表的第一个公共节点。
假设2延伸:求两个单向链表第一个公共节点有两种方法:
(1)第一种方法是:两个链表如果有 公共节点,那么从第一个公共节点开始往后的所有节点都相同,我们首先遍历两个链表,求出两个链表的长度。然后设定两个指针分别指向这两个单向链表的头结 点,首先让链表较长的指针先移动,直至移动的剩余长度与链表较短的那个链表长度相同为止,这个时候两个指针开始同时移动,直到两个指针指向的节点相同,这 个节点便是这两个单向链表的第一个公共节点。
(2)第二种方法是:可以设置两个栈,首先分别把两个链表中的节点依次入栈,接下来从两个栈中同时一个一个的将节点弹出,遇到第一对弹出的节点不同时,那么前一次弹出相同的节点便是这两个链表的第一个公共节点。

【重点】假设3:这个二叉树是普通的二叉树,并且不存在指向父节点的指针

<方法1>:判断子树中是否存在某节点(时间复杂度 O ( N 2 ) O(N^2) O(N2)

首先建立一个方法判断一棵树中是否存在某个节点,这个比较简单,只需要遍历整棵树,如果存在返回true,如果不存在则返回false。
接下来的判断过程就相当于二叉排序数的判断过程了,只不过二叉排序树判断一个节点是否在左子树或者右子树中只需要和父节点比较,而普通的树需要遍历整个左子树或者右子树才能实现。
(1)如果题目给的两个节点都在右子树,那么继续遍历当前节点的右子树
(2)如果题目给的两个节点都在左子树,那么继续遍历当前节点的左子树
(3)如果给定的两个节点一个在左子树,一个在右子树,则返回当前节点。

<方法2>:寻找从根节点到子节点的路径

如果找到从根节点到两个子节点的路径,那么题目就迎刃而解了,可以用栈遍历二叉树,最后找到从根节点到相应节点的路径,类似二叉树的先序非递归遍历一样,先遍历根节点,接着遍历左子树,接着遍历右子树,直到遍历到相应节点为止,这时栈中的元素便是路径元素。

<方法3>:递归遍历

设根节点为root,两个节点分别为p,q,用前序遍历方法递归遍历整个二叉树,如果只找到p则返回p,如果只找到q则返回q,如果一个节点的左右子树分别找到p,q,则返回该节点,其他情况返回NULL。
方法1的代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class LCA {
public:
    TreeNode *commonAncestor(TreeNode *root, TreeNode *p, TreeNode *q) {
        if (!covers(root, p) || !covers(root, q))
            return null;
        return commonAncestorHelper(root, p, q);
    }
    
    bool covers(TreeNode *root, TreeNode *p) {
        if (!root) return NULL;
        if (root == p) return true;
        return covers(root->left, p) || covers(root->right, q);
    }

    TreeNode *commonAncestorHelper(TreeNode *root, TreeNode *p, 
                                   TreeNode *q) {
        if (!root) return NULL;
        if (root == p || root == q) return root;
        bool is_p_in_left = covers(root->left, p);
        bool is_q_in_right = covers(root->right, q);
        //若p和q不在同一边则返回root
        if (is_p_in_left != is_q_in_right) return root;
        //否则p和q在同一边,递归访问那一边
        TreeNode *tmp = is_p_in_left? root->left : root->right;
        return commonAncestorHelper(tmp, p, q);
    }
   
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值