Swift高效解法!一文搞懂 LeetCode 236「二叉树的最近公共祖先」,助你快速拿下面试!

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

摘要

最近公共祖先(LCA,Lowest Common Ancestor)在二叉树、二叉搜索树(BST)等数据结构中有广泛应用,比如权限管理、网络路由、基因分析等。今天我们用 Swift 来解 LeetCode 236:「二叉树的最近公共祖先」,不仅会给出代码,还会分析它的时间复杂度、空间复杂度,并结合实际场景聊聊它的应用。

问题描述

给定一个二叉树,找到两个节点的最近公共祖先(LCA)。

LCA 的定义

“对于两个节点 p 和 q,它们的最近公共祖先是树中最深的一个节点,该节点的子树同时包含 p 和 q(允许某个节点是它自己的祖先)。”

示例 1

输入

root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1

二叉树结构

        3
       / \
      5   1
     / \  / \
    6   2 0  8
       / \
      7   4

输出

3

解释:节点 3 是 5 和 1 的最近公共祖先。

示例 2

输入

root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4

二叉树结构

        3
       / \
      5   1
     / \  / \
    6   2 0  8
       / \
      7   4

输出

5

解释:5 是 5 和 4 的最近公共祖先,因为 4 是 5 的子节点。

示例 3

输入

root = [1,2], p = 1, q = 2

二叉树结构

    1
   /
  2

输出

1

解释:1 是 1 和 2 的最近公共祖先。

Swift 题解

我们可以使用递归的方法来求解 LCA。核心思路是:

  1. 递归遍历整棵树,如果当前节点 rootpq,那么 root 可能就是 LCA。
  2. 递归到左右子树寻找 p 和 q
    • 如果 pq 分别出现在左右子树,说明当前节点 root 就是最近公共祖先。
    • 如果 pq 只出现在某一侧(左或右),说明最近公共祖先在那一侧。

代码实现

class TreeNode {
    var val: Int
    var left: TreeNode?
    var right: TreeNode?
    init(_ val: Int) {
        self.val = val
        self.left = nil
        self.right = nil
    }
}

class Solution {
    func lowestCommonAncestor(_ root: TreeNode?, _ p: TreeNode?, _ q: TreeNode?) -> TreeNode? {
        // 如果 root 为空,或者 root 等于 p 或 q,直接返回 root
        if root == nil || root === p || root === q {
            return root
        }
        
        // 递归查找左右子树
        let left = lowestCommonAncestor(root?.left, p, q)
        let right = lowestCommonAncestor(root?.right, p, q)
        
        // 如果左右子树都找到 p 或 q,说明当前 root 就是 LCA
        if left != nil && right != nil {
            return root
        }
        
        // 否则,LCA 在找到的那一侧
        return left != nil ? left : right
    }
}

代码分析

递归逻辑

  • lowestCommonAncestor 方法会递归遍历二叉树,寻找 pq
  • 终止条件
    • 如果 root == nil,返回 nil,表示没找到节点。
    • 如果 root == proot == q,直接返回 root,表示找到了其中一个目标节点。
  • 递归查找
    • root.leftroot.right 中分别查找 pq
    • 如果 pq 分别位于 root 的左右子树,那么 root 就是最近公共祖先。
    • 如果 pq 只在某一侧,则返回该侧的结果。

示例测试

测试代码

func testLCA() {
    let root = TreeNode(3)
    root.left = TreeNode(5)
    root.right = TreeNode(1)
    root.left?.left = TreeNode(6)
    root.left?.right = TreeNode(2)
    root.right?.left = TreeNode(0)
    root.right?.right = TreeNode(8)
    root.left?.right?.left = TreeNode(7)
    root.left?.right?.right = TreeNode(4)

    let solution = Solution()
    
    if let lca = solution.lowestCommonAncestor(root, root.left, root.right) {
        print("最近公共祖先是: \(lca.val)") // 3
    }
    
    if let lca = solution.lowestCommonAncestor(root, root.left, root.left?.right?.right) {
        print("最近公共祖先是: \(lca.val)") // 5
    }
}

testLCA()

输出

最近公共祖先是: 3
最近公共祖先是: 5

时间复杂度

  • 最坏情况:二叉树是一个链表,需要遍历所有节点,时间复杂度是 O(N)
  • 平均情况:每个节点最多被访问一次,仍然是 O(N)

空间复杂度

  • 递归调用栈:最坏情况(退化为链表)需要 O(N) 的栈空间。
  • 平均情况:平衡二叉树情况下,递归调用栈深度为 O(log N),因此空间复杂度是 O(log N)

实际应用场景

1. 权限管理

假设一个公司的组织结构是一个树状结构,查找最近公共祖先就可以用于找出两个员工的最小共同主管。

2. 网络路由

在计算机网络中,数据包需要找到某个路由的最近公共祖先,以决定最优传输路径。

3. 生物学(基因研究)

在家族树或基因进化树中,找到两个生物物种的最近公共祖先,帮助研究它们的共同祖先。

总结

  • 这道题是典型的 二叉树递归遍历 题目,核心思想是后序遍历
  • 代码逻辑清晰,时间复杂度 O(N),空间复杂度 O(log N)~O(N)
  • 结合实际场景,LCA 可以用于权限管理、网络路由、基因研究等多个领域。

希望这篇文章对你有所帮助!如果有问题,欢迎交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

网罗开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值