剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
剑指 Offer 68 - II. 二叉树的最近公共祖先
二叉树的最近公共祖先
方法一:递归
- 对于节点node1、node2,其最近公共祖先节点parentNode的特点是以下之一:
- node1存在与parentNode左子树中,node2存在于parentNode的右子树中
- node1、node2均存在于parentNode的同一侧子树中,且node1或node2就是parentNode

- 所以,如果当前节点的左子树包含node1且右子树中包含node2,或者只有一侧子树中有node1且当前节点等于另一个node2,就找到了最近公共祖先节点。
- 所以,递归【判断当前节点的左、右子树中是否含有指定节点】
const dfs (node, p, q) {
if (!node) {
return false
}
const lHas = dfs(node.left, p, q)
const rHas = dfs(node.right, p, q)
return lHas || rHas || node.val === p.val || node.val === q.val
}
if ((lHas && rHas) ||
(lHas || rHas) && (node.val === q.val || node.val === p.val)) {
parentNode = node
}
var lowestCommonAncestor = function(root, p, q) {
let parentNode = null
const dfs = (node, p, q) => {
if (!node) {
return null
}
const lTree = dfs(node.left, p, q)
const rTree = dfs(node.right, p, q)
if ((lTree && rTree) || ((node.val === p.val || node.val === q.val) && (lTree || rTree)) ) {
parentNode = node
}
return lTree || rTree || node.val === p.val || node.val === q.val
}
dfs(root, p, q)
return parentNode
};
分治
- 这里其实用到了分治的思想,对于一棵树操作起来也许不是很容易,把它拆成了操作其左右子树。判断以节点A为根的树是否满足条件,可以先看节点A的左右子树是否满足条件,再操作自身,最后总结左子树、右子树、自己的结果。
剑指 Offer 55 - I. 二叉树的深度
剑指 Offer 55 - II. 平衡二叉树
方法二:存储父节点
- 用Map存储所有节点的父节点。然后从p开始向根节点访问,最后从q开始向上访问,遇到已访问过的节点,说明遇到了最近的公共祖先。
var levelTraversal = (root, p, q) => {
const queue = [root]
const relationMap = new Map()
relationMap.set(root, {
parent: null,
visited: false
})
let findAll = 0
while(queue.length) {
const node = queue.shift()
if (node.val === p.val || node.val === q.val) {
findAll++
}
if (findAll === 2) {
return relationMap
}
if (node.left) {
relationMap.set(node.left, {
parent: node,
visited: false
})
queue.push(node.left)
}
if (node.right) {
relationMap.set(node.right, {
parent: node,
visited: false
})
queue.push(node.right)
}
}
}
var lowestCommonAncestor = function(root, p, q) {
if (!root) { return null }
const relationMap = levelTraversal(root, p, q)
let pNode = relationMap.get(p)
let qNode = relationMap.get(q)
if (pNode && qNode) {
while (pNode && !pNode.visited) {
pNode.visited = true
pNode = relationMap.get(pNode.parent)
}
while (qNode && !qNode.visited) {
q = qNode.parent
qNode = relationMap.get(qNode.parent)
}
}
return q
};
层级遍历
- 在寻找路径时,一种方法是使用这种记录节点和其父节点的映射,这种映射通常需要借助层级遍历。
剑指 Offer 34. 二叉树中和为某一值的路径
二叉搜索树的最近公共祖先
- 二叉搜索树是二叉树的特殊一种,所以以上两种方法也可以适用。
- 但是,由于二叉树的特点,即左子树都比根节点小,右子树都比根节点大,所以可以在递归上有所变化。
- 对于root、p、q的值,只有三种情况:
- p和q都小于root:p和q的最近公共祖先一定在root的左子树中
- p和q都大于root:p和q的最近公共祖先一定在root的右子树中
- 其他,即root在p、q之间:说明在root处分岔了,那root就是p和q的最近公共子祖先

var lowestCommonAncestor = function(root, p, q) {
return dfs(root, p, q)
};
var dfs = (node, p, q) => {
if (!node) { return null }
if (node.val > p.val && node.val > q.val) {
return dfs(node.left, p, q)
}
if (node.val < p.val && node.val < q.val) {
return dfs(node.right, p, q)
}
return node
}