文章目录
1. 题目来源
链接:I. 二叉搜索树的最近公共祖先
链接:II. 二叉树的最近公共祖先
来源:LeetCode——《剑指-Offer》专项
2. 题目说明
3. 题目解析 — I. 二叉搜索树的最近公共祖先
方法一:递归+巧妙解法
这道题可以用递归来求解,由于二叉搜索树的特点是 左<根<右, 所以根节点的值一直都是中间值,大于左子树的所有节点值,小于右子树的所有节点值,那么可以做如下的判断,如果根节点的值大于 p
和 q
之间的较大值,说明 p
和 q
都在左子树中,那么此时我们就进入根节点的左子节点继续递归,如果根节点小于 p
和 q
之间的较小值,说明 p
和 q
都在右子树中,那么此时我们就进入根节点的右子节点继续递归,如果都不是,则说明当前根节点就是最小共同父节点,直接返回即可。
参见代码如下:
// 执行用时 :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
问题。
和上个问题必将,只能在二叉树中来搜索 p
和 q
,然后从路径中找到最后一个相同的节点即为父节点,可以用递归来实现,在递归函数中,首先看当前结点是否为空,若为空则直接返回空,若为 p
或 q
中的任意一个,也直接返回当前结点。否则的话就对其左右子结点分别调用递归函数,由于这道题限制了 p
和 q
一定都在二叉树中存在,那么如果当前结点不等于 p
或 q
,p
和 q
要么分别位于左右子树中,要么同时位于左子树,或者同时位于右子树,那么分别来讨论:
-
若
p
和q
分别位于左右子树中,那么对左右子结点调用递归函数,会分别返回p
和q
结点的位置,而当前结点正好就是p
和q
的最小共同父结点,直接返回当前结点即可,这就是题目中的例子 1 的情况 -
若
p
和q
同时位于左子树,这里有两种情况,一种情况是left
会返回p
和q
中较高的那个位置,而right
会返回空,所以最终返回非空的left
即可,这就是题目中的例子 2 的情况。还有一种情况是会返回p
和q
的最小父结点,就是说当前结点的左子树中的某个结点才是p
和q
的最小父结点,会被返回 -
若
p
和q
同时位于右子树,同样这里有两种情况,一种情况是right
会返回p
和q
中较高的那个位置,而left
会返回空,所以最终返回非空的right
即可,还有一种情况是会返回p
和q
的最小父结点,就是说当前结点的右子树中的某个结点才是p
和q
的最小父结点,会被返回
参见代码如下:
// 执行用时 :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
,那么根据上面的分析,p
和 q
的位置就有三种情况,p
和 q
要么分别位于左右子树中,要么同时位于左子树,或者同时位于右子树。需要优化的情况就是当 p
和 q
同时为于左子树或右子树中,而且返回的结点并不是 p
或 q
,那么就是 p
和 q
的最小父结点了,已经求出来了,就不用再对右结点调用递归函数了,这是为啥呢?因为根本不会存在 left
既不是 p
也不是 q
,同时还有 p
或者 q
在 right
中。首先递归的第一句就限定了只要遇到了 p
或者 q
,就直接返回,之后又限定了只有当 left
和 right
同时存在的时候,才会返回当前结点,当前结点若不是 p
或 q
,则一定是最小父节点,否则 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;
}
};