LC.235 | 二叉搜索树的最近公共祖先 | 树 | 双解法:路径记录 vs 分岔点

输入:
二叉搜索树的根节点 root,以及两个指定节点 pq

要求:
找到这两个节点的最近公共祖先(LCA)。

输出:
最近公共祖先节点的指针 TreeNode*


思路:

本题提供了两种思路,从“直观通用”到“利用特性优化”:

1. 解法一:路径记录法(哈希表)

  • 核心思想:将“找祖先”转化为“找路径交集”。
  • 第一步:利用 BST 的性质(左<根<右),从根节点出发找到节点 p。在寻找过程中,将经过的所有节点(包括 p 自身)存入一个哈希集合 unordered_set 中。这相当于记录了 p 的所有祖先。
  • 第二步:同样从根节点出发去找节点 q。在路径上,每次经过一个节点,都检查它是否存在于 Set 中:
    • 如果存在,说明这个节点是 pq 的公共祖先,我们用变量 latest 记录下来。
    • 由于我们是从上往下遍历的,最后一个被记录的 latest 一定是深度最深的那个公共祖先。
    • 如果遇到一个不存在哈希表 的节点,说明路径从此开始分岔了,之后的节点不可能是公共祖先了,直接 break,返回当前的 latest 即可。

2. 解法二:利用 BST 性质找分岔点(最优解)

  • 核心思想:利用数值大小关系,直接定位“分岔点”。
  • 在二叉搜索树中,如果 pq 分别位于当前节点的两侧(一个大一个小),或者其中一个就是当前节点,那么当前节点一定是最近公共祖先。
  • 逻辑
    • 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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值