美文网首页
剑指offer 76- 树中两个结点的最低公共祖先

剑指offer 76- 树中两个结点的最低公共祖先

作者: 顾子豪 | 来源:发表于2021-06-11 00:38 被阅读0次

    给出一个二叉树,输入两个树节点,求它们的最低公共祖先。

    一个树节点的祖先节点包括它本身。

    注意:

    输入的二叉树不为空;
    输入的两个节点一定不为空,且是二叉树中的节点;

    样例

    二叉树[8, 12, 2, null, null, 6, 4, null, null, null, null]如下图所示:
        8
       / \q
      12  2
         / \
        6   4
    
    1. 如果输入的树节点为2和12,则输出的最低公共祖先为树节点8。
    
    2. 如果输入的树节点为2和6,则输出的最低公共祖先为树节点2。
    

    分析:
    算法一:递归

    若 rootrootroot 是 p,q 的 最近公共祖先 ,则只可能为以下情况之一:

    • p 和 q在 root 的子树中,且分列 root的 异侧(即分别在左、右子树中);
    • p=root ,且 q 在 root 的左或右子树中;
    • q=root ,且 p 在 root 的左或右子树中;

    递归解析:

    • 终止条件:
      当越过叶节点,则直接返回 null;
      当 root 等于 p,q ,则直接返回 root ;

    • 递推过程:
      递归左子节点,返回值记为 left ;
      递归右子节点,返回值记为 right ;

    • 返回值: 根据 left 和 right ,可展开为四种情况;
      当 left 和 right同时为空 :说明 root的左 / 右子树中都不包含 p,q ,返回 null ;
      当 left 和 right 同时不为空 :说明 p,q 分列在 root 的 异侧 (分别在 左 / 右子树),因此 root 为最近公共祖先,返回 root ;
      当 left 为空 ,right 不为空 :p,q 都不在 root 的左子树中,直接返回 right 。具体可分为两种情况:
      p,q 其中一个在 root的 右子树 中,此时 right 指向 p(假设为 p );
      p,q 两节点都在 roo 的 右子树 中,此时的 right 指向 最近公共祖先节点 ;
      当 left不为空 , right 为空 :同理;

    /**
     * Definition for a binary tree node.
     * struct TreeNode {
     *     int val;
     *     TreeNode *left;
     *     TreeNode *right;
     *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     * };
     */
    class Solution {
    public:
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
            if(!root || root==p || root==q) return root;//递归终止条件
            auto left = lowestCommonAncestor(root->left, p, q);
            auto right = lowestCommonAncestor(root->right, p, q);
            if(left && right) return root;//两个节点分别位于左子树和右子树
            if(!left) return right;//左子树中两个节点都找不到
            return left;//右子树中两个节点都找不到
        }
    };
    

    复杂度分析

    • 时间复杂度:O(N),其中 NNN 是二叉树的节点数。二叉树的所有节点有且只会被访问一次,因此时间复杂度为 O(N)。

    • 空间复杂度:O(N) ,其中 NNN 是二叉树的节点数。递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 NNN,因此空间复杂度为 O(N)。

    算法二:存储父节点

    我们可以用哈希表存储所有节点的父节点,然后我们就可以利用节点的父节点信息从 p 结点开始不断往上跳,并记录已经访问过的节点,再从 q 节点开始不断往上跳,如果碰到已经访问过的节点,那么这个节点就是我们要找的最近公共祖先。

    从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
    从 p 节点开始不断往它的祖先移动,并用数据结构记录已经访问过的祖先节点。
    同样,我们再从 q 节点开始不断往它的祖先移动,如果有祖先已经被访问过,即意味着这是 p 和 q 的深度最深的公共祖先。

    复杂度分析

    • 时间复杂度:O(N),其中 N 是二叉树的节点数。二叉树的所有节点有且只会被访问一次,从 p 和 q 节点往上跳经过的祖先节点个数不会超过 N,因此总的时间复杂度为 O(N)。

    • 空间复杂度:O(N),其中 N 是二叉树的节点数。递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 N,因此空间复杂度为 O(N),哈希表存储每个节点的父节点也需要 O(N)的空间复杂度,因此最后总的空间复杂度为 O(N)。

    /**
     * Definition for a binary tree node.
     * struct TreeNode {
     *     int val;
     *     TreeNode *left;
     *     TreeNode *right;
     *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     * };
     */
    class Solution {
    public:
        unordered_map<TreeNode*, TreeNode*> father;
        unordered_map<TreeNode*, bool> vis;
        void dfs(TreeNode* root) {
            if(root->left) father[root->left] = root, dfs(root->left);
            if(root->right) father[root->right] = root, dfs(root->right);
        }
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {   
            father[root] = nullptr;
            dfs(root);
            while(p) vis[p] = true, p = father[p];
            while(q) {
                if(vis[q]) return q;
                q = father[q];
            }
            return nullptr;
        }
    };
    

    相关文章

      网友评论

          本文标题:剑指offer 76- 树中两个结点的最低公共祖先

          本文链接:https://www.haomeiwen.com/subject/escxsltx.html