236. 二叉树的最近公共祖先 🟨
题目
leetcode 中等
题目
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
最近公共祖先的定义为:“对于有根树 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 。
输入:p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
题解
方法:递归
解题思路
我们要找节点 p 和 q 的最近公共祖先。从根节点往下找,如果一个节点的左子树包含了 p,右子树包含了 q(或者反过来),那么这个节点一定就是它们的最近公共祖先。
我们可以通过 后序遍历(自底向上) 将寻找的结果一层层向上传递:
- 终止条件:
- 越过叶子节点,返回 null。
- 如果当前节点等于 p 或 q,说明找到了目标节点,直接返回当前节点。
- 递归工作:
- 递归搜索左子树,返回值记为 left。
- 递归搜索右子树,返回值记为 right。
- 根据返回值判断:
- 情况 1:如果 left 和 right 都不为空,说明 p 和 q 分别在当前节点的两侧,当前节点就是我们要找的最近公共祖先,返回 root。
- 情况 2:如果 left 为空,right 不为空,说明 p 和 q 都不在左子树,直接返回 right。
- 情况 3:如果 right 为空,left 不为空,同理返回 left。
- 情况 4:如果 left 和 right 都为空,说明当前节点的左右子树都没有 p 和 q,返回 null。
复杂度
- 时间复杂度:O(N)
- 空间复杂度:O(N)
js
function lowestCommonAncestor(root, p, q) {
console.log('♻️ root', root ? root.val : null)
// 1. 递归终止条件
// 如果走到空节点,或者找到了 p 或 q,直接返回
if (root === null || root === p || root === q) {
console.log('🛑 停止', root ? root.val : null)
return root
}
// 2. 递归遍历左右子树
const left = lowestCommonAncestor(root.left, p, q)
const right = lowestCommonAncestor(root.right, p, q)
// 3. 处理返回值
// 如果 left 和 right 都非空,说明 p 和 q 分布在 root 的两侧,root 就是最近公共祖先
if (left !== null && right !== null) {
console.log('找到最近公共祖先', root.val)
return root
}
// 如果只有一个非空,说明 p 和 q 在同一侧,返回非空的那个节点(它或者是 p/q,或者是最近公共祖先)
// 如果都为空,返回 null
const result = left !== null ? left : right
console.log('返回', result)
return result
}
class TreeNode {
constructor(val, left = null, right = null) {
this.val = val
this.left = left
this.right = right
}
}
const tree = new TreeNode(3)
tree.left = new TreeNode(
5,
new TreeNode(6),
new TreeNode(2, new TreeNode(7), new TreeNode(4)),
)
tree.right = new TreeNode(1, new TreeNode(0), new TreeNode(8))
console.log('🌰', lowestCommonAncestor(tree, tree.left, tree.right.right)) // 5 和 8 的最近公共祖先是 3
console.log('--------------')
console.log('🌰', lowestCommonAncestor(tree, tree.left, tree.left.right.right)) // 5 和 4 的最近公共祖先是 5
执行结果
♻️ root 3
♻️ root 5
🛑 停止 5
♻️ root 1
♻️ root 0
♻️ root null
🛑 停止 null
♻️ root null
🛑 停止 null
返回 null
♻️ root 8
🛑 停止 8
返回 TreeNode { val: 8, left: null, right: null }
找到最近公共祖先 3
🌰 TreeNode {
val: 3,
left: TreeNode {
val: 5,
left: TreeNode { val: 6, left: null, right: null },
right: TreeNode {
val: 2,
left: TreeNode { val: 7, left: null, right: null },
right: TreeNode { val: 4, left: null, right: null }
}
},
right: TreeNode {
val: 1,
left: TreeNode { val: 0, left: null, right: null },
right: TreeNode { val: 8, left: null, right: null }
}
}
--------------
♻️ root 3
♻️ root 5
🛑 停止 5
♻️ root 1
♻️ root 0
♻️ root null
🛑 停止 null
♻️ root null
🛑 停止 null
返回 null
♻️ root 8
♻️ root null
🛑 停止 null
♻️ root null
🛑 停止 null
返回 null
返回 null
返回 TreeNode {
val: 5,
left: TreeNode { val: 6, left: null, right: null },
right: TreeNode {
val: 2,
left: TreeNode { val: 7, left: null, right: null },
right: TreeNode { val: 4, left: null, right: null }
}
}
🌰 TreeNode {
val: 5,
left: TreeNode { val: 6, left: null, right: null },
right: TreeNode {
val: 2,
left: TreeNode { val: 7, left: null, right: null },
right: TreeNode { val: 4, left: null, right: null }
}
}
方法:迭代
解题思路
- 从根节点开始遍历二叉树(DFS或BFS均可),用一个 Map 记录每个节点的父节点(Map<当前节点, 父节点>)。
- 直到把 p 和 q 的父节点都记录下来。
- 从节点 p 开始,利用 Map 不断往上跳,把 p 的所有祖先节点记录到一个 Set 中。
- 从节点 q 开始,利用 Map 不断往上跳,每次跳都检查当前节点是否在刚刚的 Set 中。第一个在 Set 中出现的节点,就是最近公共祖先!
复杂度
- 时间复杂度:O(N)
- 空间复杂度:O(N)
js
function lowestCommonAncestor(root, p, q) {
// 使用 Map 记录每个节点的父节点:key 为当前节点,value 为父节点
const parentMap = new Map()
const queue = [root]
parentMap.set(root, null) // 根节点没有父节点
// BFS 遍历,直到 p 和 q 的父节点都被我们记录下来
while (!parentMap.has(p) || !parentMap.has(q)) {
const node = queue.shift()
if (node.left) {
parentMap.set(node.left, node)
queue.push(node.left)
}
if (node.right) {
parentMap.set(node.right, node)
queue.push(node.right)
}
console.log(
'queue',
queue.map(n => n.val),
)
console.log(
'parentMap',
JSON.stringify(
Array.from(parentMap.entries()).map(([node, parent]) => [
node.val,
parent ? parent.val : null,
]),
),
)
}
// 记录 p 的所有祖先
const ancestors = new Set()
let current = p
while (current !== null) {
console.log('p current', current.val)
ancestors.add(current)
current = parentMap.get(current) // 向上找父节点
console.log(
'ancestors',
Array.from(ancestors).map(node => node.val),
)
}
// 从 q 开始向上找,遇到第一个存在于 ancestors 集合中的节点就是 LCA
current = q
while (!ancestors.has(current)) {
console.log('q current', current.val)
current = parentMap.get(current)
console.log(
'ancestors',
Array.from(ancestors).map(node => node.val),
)
}
return current
}
class TreeNode {
constructor(val, left = null, right = null) {
this.val = val
this.left = left
this.right = right
}
}
const tree = new TreeNode(3)
tree.left = new TreeNode(
5,
new TreeNode(6),
new TreeNode(2, new TreeNode(7), new TreeNode(4)),
)
tree.right = new TreeNode(1, new TreeNode(0), new TreeNode(8))
console.log('🌰', lowestCommonAncestor(tree, tree.left, tree.right.right)) // 5 和 8 的最近公共祖先是 3
console.log('--------------')
console.log('🌰', lowestCommonAncestor(tree, tree.left, tree.left.right.right)) // 5 和 4 的最近公共祖先是 5
执行结果
queue [ 5, 1 ]
parentMap [[3,null],[5,3],[1,3]]
queue [ 1, 6, 2 ]
parentMap [[3,null],[5,3],[1,3],[6,5],[2,5]]
queue [ 6, 2, 0, 8 ]
parentMap [[3,null],[5,3],[1,3],[6,5],[2,5],[0,1],[8,1]]
p current 5
ancestors [ 5 ]
p current 3
ancestors [ 5, 3 ]
q current 8
ancestors [ 5, 3 ]
q current 1
ancestors [ 5, 3 ]
🌰 TreeNode {
val: 3,
left: TreeNode {
val: 5,
left: TreeNode { val: 6, left: null, right: null },
right: TreeNode {
val: 2,
left: TreeNode { val: 7, left: null, right: null },
right: TreeNode { val: 4, left: null, right: null }
}
},
right: TreeNode {
val: 1,
left: TreeNode { val: 0, left: null, right: null },
right: TreeNode { val: 8, left: null, right: null }
}
}
--------------
queue [ 5, 1 ]
parentMap [[3,null],[5,3],[1,3]]
queue [ 1, 6, 2 ]
parentMap [[3,null],[5,3],[1,3],[6,5],[2,5]]
queue [ 6, 2, 0, 8 ]
parentMap [[3,null],[5,3],[1,3],[6,5],[2,5],[0,1],[8,1]]
queue [ 2, 0, 8 ]
parentMap [[3,null],[5,3],[1,3],[6,5],[2,5],[0,1],[8,1]]
queue [ 0, 8, 7, 4 ]
parentMap [[3,null],[5,3],[1,3],[6,5],[2,5],[0,1],[8,1],[7,2],[4,2]]
p current 5
ancestors [ 5 ]
p current 3
ancestors [ 5, 3 ]
q current 4
ancestors [ 5, 3 ]
q current 2
ancestors [ 5, 3 ]
🌰 TreeNode {
val: 5,
left: TreeNode { val: 6, left: null, right: null },
right: TreeNode {
val: 2,
left: TreeNode { val: 7, left: null, right: null },
right: TreeNode { val: 4, left: null, right: null }
}
}