# 题目
给定一个二叉树,找到该树中两个指定节点的最近公共祖先。
百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
输入:root = [1,2], p = 1, q = 2
输出:1
好笨啊,看看题解吧
# 递归
Moment
当我们用递归去做这个题时不要被题目误导,应该要明确一点
这个函数的功能有三个:给定两个节点 p 和 q
- 如果 p 和 q 都存在,则返回它们的公共祖先;
- 如果只存在一个,则返回存在的一个;
- 如果 p 和 q 都不存在,则返回 NULL
本题说给定的两个节点都存在,那自然还是能用上面的函数来解决
具体思路
- 如果当前结点 root 等于 NULL,则直接返回 NULL
- 如果 root 等于 p 或者 q ,那这棵树一定返回 p 或者 q
- 然后递归左右子树,因为是递归,使用函数后可认为左右子树已经算出结果,用 left 和 right 表示
- 此时若 left 为空,那最终结果只要看 right;若 right 为空,那最终结果只要看 left
- 如果 left 和 right 都非空,因为只给了 p 和 q 两个结点,都非空,说明一边一个,因此 root 是他们的最近公共祖先
- 如果 left 和 right 都为空,则返回空(其实已经包含在前面的情况中了)
时间复杂度是 O (n):每个结点最多遍历一次或用主定理,空间复杂度是 O (n):需要系统栈空间
class Solution { | |
public: | |
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { | |
if(root == NULL) | |
return NULL; | |
if(root == p || root == q) | |
return root; | |
TreeNode* left = lowestCommonAncestor(root->left, p, q); | |
TreeNode* right = lowestCommonAncestor(root->right, p, q); | |
if(left == NULL) | |
return right; | |
if(right == NULL) | |
return left; | |
if(left && right) //p 和 q 在两侧 | |
return root; | |
return NULL; // 必须有返回值 | |
} | |
}; |
# 暴力
old8
一次 dfs 维护出每个点的深度和父亲,查询时先将两点定到相同深度,之后一起往上跳。整体复杂度为 O (nlogn)。
/** | |
* 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) { | |
map<TreeNode*, int> vis; | |
map<TreeNode*, TreeNode*> pre; | |
function<void(TreeNode*, TreeNode*, int)> dfs = [&](TreeNode* fa, TreeNode* rt, int dep) { | |
vis[rt] = dep; | |
pre[rt] = fa; | |
if (rt->left) dfs(rt, rt->left, dep + 1); | |
if (rt->right) dfs(rt, rt->right, dep + 1); | |
}; | |
dfs(NULL, root, 1); | |
if (p == q) return p; | |
while (vis[p] > vis[q] && pre[p] != NULL) p = pre[p]; | |
while (vis[p] < vis[q] && pre[q] != NULL) q = pre[q]; | |
while (p != q) { | |
p = pre[p]; | |
q = pre[q]; | |
} | |
return p; | |
} | |
}; |
# 倍增优化 DP
维护 f (u, k) 表示 u 节点的第 个祖先,更新时一次 dfs,f (u, k + 1) = f (f (u, k), k),查询的时候类似暴力解法,不过向上跳可以跳得步伐大很多。对于本题预处理复杂度为 O (nlogn),查询复杂度为 O (2logn)。
/** | |
* 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) { | |
const int maxf = 22; | |
map<TreeNode*, int> vis; | |
map<TreeNode*, TreeNode*> pre[maxf]; | |
function<void(TreeNode*, TreeNode*, int)> dfs = [&](TreeNode* fa, TreeNode* rt, int dep) { | |
vis[rt] = dep; | |
pre[0][rt] = fa; | |
for (int i = 0; (1 << i) <= vis[fa] && i < maxf; i++) { | |
pre[i + 1][rt] = pre[i][pre[i][rt]]; | |
} | |
if (rt->left) dfs(rt, rt->left, dep + 1); | |
if (rt->right) dfs(rt, rt->right, dep + 1); | |
}; | |
dfs(NULL, root, 1); | |
if (vis[p] < vis[q]) swap(p, q); | |
for (int i = maxf - 1; i >= 0; i--) { | |
if (vis[p] >= (1 << i) && vis[pre[i][p]] >= vis[q]) p = pre[i][p]; | |
if (p == q) return p; | |
} | |
for (int i = maxf - 1; i >= 0; i--) { | |
if (vis[p] >= (1 << i) && pre[i][p] != pre[i][q]) { | |
p = pre[i][p]; | |
q = pre[i][q]; | |
} | |
} | |
return pre[0][p]; | |
} | |
}; |
# 思考
Krahets
何为最近公共祖先?
设节点 root 为节点 p,q 的某公共祖先,若其左子节点 root.left 和右子节点 root.right 都不是 p,q 的公共祖先,则称 root 是 “最近的公共祖先”
根据以上定义,若 root 是 p,q 的最近最近公共祖先,则只可能为以下的情况之一:
- p,q 位于 root 异侧(分别在左右子树中)
- p = root,且 q 在 root 的左或右子树中
- q = root,且 p 在 root 的左或右子树中
比如,上图中的 7,8,它们的最近公共祖先为 3,因为 5 不是 8 的祖先,1 不为 7 的祖先。
递归解析
- 终止条件
- 当越过叶节点,直接返回 null;
- 当 root 等于 p,q,则直接返回 root;
- 递归工作
- 开启递归左子节点,返回值记为 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 两节点都在 root 的右子树中,此时的 right 指向最近公共祖先节点;
- 当 left 不为空,right 为空,与前一种情况同理
class Solution { | |
public: | |
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { | |
if(root == nullptr || root == p || root == q) return root; | |
TreeNode *left = lowestCommonAncestor(root->left, p, q); | |
TreeNode *right = lowestCommonAncestor(root->right, p, q); | |
if(left == nullptr && right == nullptr) return nullptr; // 1. | |
if(left == nullptr) return right; // 3. | |
if(right == nullptr) return left; // 4. | |
return root; // 2. if(left != null and right != null) | |
} | |
}; | |
/* 观察发现, 情况 1. 可合并至 3. 和 4. 内 */ | |
// class Solution { | |
// public: | |
// TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { | |
// if(root == nullptr || root == p || root == q) return root; | |
// TreeNode *left = lowestCommonAncestor(root->left, p, q); | |
// TreeNode *right = lowestCommonAncestor(root->right, p, q); | |
// if(left == nullptr) return right; | |
// if(right == nullptr) return left; | |
// return root; | |
// } | |
// }; |
- 时间复杂度 O (N) : 其中 NN 为二叉树节点数;最差情况下,需要递归遍历树的所有节点。
- 空间复杂度 O (N) : 最差情况下,递归深度达到 N ,系统使用 O (N) 大小的额外空间。