输入:
二叉搜索树的根节点 root,以及两个指定节点 p 和 q。
要求:
找到这两个节点的最近公共祖先(LCA)。
输出:
最近公共祖先节点的指针 TreeNode*。
思路:
本题提供了两种思路,从“直观通用”到“利用特性优化”:
1. 解法一:路径记录法(哈希表)
- 核心思想:将“找祖先”转化为“找路径交集”。
- 第一步:利用 BST 的性质(左<根<右),从根节点出发找到节点
p。在寻找过程中,将经过的所有节点(包括p自身)存入一个哈希集合unordered_set中。这相当于记录了p的所有祖先。 - 第二步:同样从根节点出发去找节点
q。在路径上,每次经过一个节点,都检查它是否存在于Set中:- 如果存在,说明这个节点是
p和q的公共祖先,我们用变量latest记录下来。 - 由于我们是从上往下遍历的,最后一个被记录的
latest一定是深度最深的那个公共祖先。 - 如果遇到一个不存在于
哈希表的节点,说明路径从此开始分岔了,之后的节点不可能是公共祖先了,直接 break,返回当前的latest即可。
- 如果存在,说明这个节点是
2. 解法二:利用 BST 性质找分岔点(最优解)
- 核心思想:利用数值大小关系,直接定位“分岔点”。
- 在二叉搜索树中,如果
p和q分别位于当前节点的两侧(一个大一个小),或者其中一个就是当前节点,那么当前节点一定是最近公共祖先。 - 逻辑:
- 若
p, q都比root小 -> 往左走。 - 若
p, q都比root大 -> 往右走。 - 否则 -> 找到分岔点,返回
root。
- 若
复杂度:
- 解法一(哈希记录):
- 时间复杂度:O(H),需要遍历两条路径。
- 空间复杂度:O(H),需要一个 哈希表 存储路径节点。
- 解法二(迭代优化):
- 时间复杂度:O(H)。
- 空间复杂度:O(1),不需要额外存储空间。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// 【解法二:最优解 - 迭代法利用 BST 性质】
// 空间复杂度 O(1)
return solveOptimized(root, p, q);
// 【解法一:通用思路 - 路径记录法】
// 空间复杂度 O(H),如果需要查看此思路,请取消注释
// return solveWithMap(root, p, q);
}
// ---------------------------------------------------------
// 解法二实现:利用 BST 剪枝(找分岔点 此法优先 哈希表只是更为通用)
// ---------------------------------------------------------
TreeNode* solveOptimized(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* current = root;
while (current) {
// 如果 p 和 q 都在当前节点的右侧,说明 LCA 在右子树
if (current->val < p->val && current->val < q->val) {
current = current->right;
}
// 如果 p 和 q 都在当前节点的左侧,说明 LCA 在左子树
else if (current->val > p->val && current->val > q->val) {
current = current->left;
}
// 否则,说明 p 和 q 分居两侧,或者 current 就是 p 或 q 之一
// 此时找到了最近公共祖先
else {
return current;
}
}
return nullptr;
}
// ---------------------------------------------------------
// 解法一实现:路径 Map/Set 记录
// ---------------------------------------------------------
TreeNode* solveWithMap(TreeNode* root, TreeNode* p, TreeNode* q) {
unordered_set<TreeNode*> pathMap;
TreeNode* curr = root;
// 1. 记录通往 p 的路径
while (curr) {
pathMap.insert(curr);
if (curr->val == p->val) break;
// BST 导航
if (curr->val > p->val) curr = curr->left;
else curr = curr->right;
}
// 2. 走通往 q 的路径,并不断更新“最近遇到的熟人”
curr = root;
TreeNode* ancestor = root;
while (curr) {
// 只要当前节点还在 p 的路径里,它就是潜在的 LCA
if (pathMap.count(curr)) {
ancestor = curr; // 更新最新的祖先
} else {
// 一旦遇到不在 pathMap 里的节点,说明分道扬镳了,不仅不用找了,
// 而且说明上一个记录的 ancestor 就是分岔点
break;
}
if (curr->val == q->val) break;
// BST 导航
if (curr->val > q->val) curr = curr->left;
else curr = curr->right;
}
return ancestor;
}
};
839

被折叠的 条评论
为什么被折叠?



