[剑指-Offer] 68 - I. 二叉搜索树的最近公共祖先及II. 二叉树的最近公共祖先(二叉树、LCA问题、递归优化、巧妙解法)

1. 题目来源

链接:I. 二叉搜索树的最近公共祖先
链接:II. 二叉树的最近公共祖先
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

在这里插入图片描述
在这里插入图片描述

3. 题目解析 — I. 二叉搜索树的最近公共祖先

方法一:递归+巧妙解法

这道题可以用递归来求解,由于二叉搜索树的特点是 左<根<右, 所以根节点的值一直都是中间值,大于左子树的所有节点值,小于右子树的所有节点值,那么可以做如下的判断,如果根节点的值大于 pq 之间的较大值,说明 pq 都在左子树中,那么此时我们就进入根节点的左子节点继续递归,如果根节点小于 pq 之间的较小值,说明 pq 都在右子树中,那么此时我们就进入根节点的右子节点继续递归,如果都不是,则说明当前根节点就是最小共同父节点,直接返回即可。

参见代码如下:

// 执行用时 :44 ms, 在所有 C++ 提交中击败了55.33%的用户
// 内存消耗 :24.9 MB, 在所有 C++ 提交中击败了100.00%的用户

/**
 * 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) return nullptr;
        if (root->val > max(p->val, q->val)) 
            return lowestCommonAncestor(root->left, p, q);
        else if (root->val < min(p->val, q->val)) 
            return lowestCommonAncestor(root->right, p, q);
        else return root;
    }
};

方法二:迭代+巧妙解法

当然,非递归的写法也是可行的,用个 while 循环来代替递归调用即可,然后不停的更新当前的根节点,也能实现同样的效果。

参见代码如下:

// 执行用时 :36 ms, 在所有 C++ 提交中击败了90.13%的用户
// 内存消耗 :25.3 MB, 在所有 C++ 提交中击败了100.00%的用户

/**
 * 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) {
        while (true) {
            if (root->val > max(p->val, q->val)) root = root->left;
            else if (root->val < min(p->val, q->val)) root = root->right;
            else break;
        }      
        return root;
    }
};

4. 题目解析 — II. 二叉树的最近公共祖先

方法一:递归+巧妙解法

典型的 LCA 问题。

和上个问题必将,只能在二叉树中来搜索 pq,然后从路径中找到最后一个相同的节点即为父节点,可以用递归来实现,在递归函数中,首先看当前结点是否为空,若为空则直接返回空,若为 pq 中的任意一个,也直接返回当前结点。否则的话就对其左右子结点分别调用递归函数,由于这道题限制了 pq 一定都在二叉树中存在,那么如果当前结点不等于 pqpq 要么分别位于左右子树中,要么同时位于左子树,或者同时位于右子树,那么分别来讨论:

  • pq 分别位于左右子树中,那么对左右子结点调用递归函数,会分别返回 pq 结点的位置,而当前结点正好就是 pq 的最小共同父结点,直接返回当前结点即可,这就是题目中的例子 1 的情况

  • pq 同时位于左子树,这里有两种情况,一种情况是 left 会返回 pq 中较高的那个位置,而 right 会返回空,所以最终返回非空的 left 即可,这就是题目中的例子 2 的情况。还有一种情况是会返回 pq 的最小父结点,就是说当前结点的左子树中的某个结点才是 pq 的最小父结点,会被返回

  • pq 同时位于右子树,同样这里有两种情况,一种情况是 right 会返回 pq 中较高的那个位置,而 left 会返回空,所以最终返回非空的 right 即可,还有一种情况是会返回 pq 的最小父结点,就是说当前结点的右子树中的某个结点才是 pq 的最小父结点,会被返回

参见代码如下:

// 执行用时 :12 ms, 在所有 C++ 提交中击败了99.60%的用户
// 内存消耗 :16.2 MB, 在所有 C++ 提交中击败了100.00%的用户

/**
 * 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 || p == root || q == root) return root;
       TreeNode *left = lowestCommonAncestor(root->left, p, q);
       TreeNode *right = lowestCommonAncestor(root->right, p, q);
       if (left && right) return root;
       return left ? left : right;
    }
};

方法二:递归优化+巧妙解法

方法一代码可以进行优化一下,如果当前结点不为空,且既不是 p 也不是 q,那么根据上面的分析,pq 的位置就有三种情况,pq 要么分别位于左右子树中,要么同时位于左子树,或者同时位于右子树。需要优化的情况就是当 pq 同时为于左子树或右子树中,而且返回的结点并不是 pq,那么就是 pq 的最小父结点了,已经求出来了,就不用再对右结点调用递归函数了,这是为啥呢?因为根本不会存在 left 既不是 p 也不是 q,同时还有 p 或者 qright 中。首先递归的第一句就限定了只要遇到了 p 或者 q,就直接返回,之后又限定了只有当 leftright 同时存在的时候,才会返回当前结点,当前结点若不是 pq,则一定是最小父节点,否则 left 一定是 p 或者 q。这里的逻辑比较绕,不太好想,画画图、多想想应该可以理清头绪。

参见代码如下:

// 执行用时 :12 ms, 在所有 C++ 提交中击败了93.83%的用户
// 内存消耗 :23.7 MB, 在所有 C++ 提交中击败了100.00%的用户

/**
 * 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:
    bool isBalanced(TreeNode* root) {
        int depth = 0;
        return help(root, &depth);
    }

    bool help(TreeNode* root, int* pDepth) {
        if (root == nullptr) {
            *pDepth = 0;
            return true;
        }
        int left;
        int right;
        if (help(root->left, &left) and help(root->right, &right)) {
            int diff = left - right;
            if (diff <= 1 and diff >= -1) {
                *pDepth = 1 + (left > right ? left : right);
                return true;
            }
        }
        return false;
    }
};
发布了343 篇原创文章 · 获赞 197 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/104788609
今日推荐