记录 1 道算法题
首个共同祖先
要求:给一个二叉树和其中两个节点,返回这两个节点的最近共同祖先。
相关题目:二叉搜索树的最近公共祖先
要找公共的祖先只能遍历一遍二叉树,使用后序遍历的话是从下向上遍历二叉树,也比较比较符合找东西的思维,看什么时候合流。
关键是递归的返回值,递归只有一个返回值,另外我们要如何根据这个返回值继续计算和向上返回。
首先找到这个公共节点后怎么存,能不能返回节点?假设后序遍历我们在匹配上的时候将这个节点的父亲也就是 root 返回。这个返回值会作为上一层递归的 left 或者 right,这时候 left 和 right 变成不匹配,也就是说没办法将是否匹配的结果一路返回。
那么怎么返回匹配成功的结果呢?
true 和 false 可以明确知道有没有返回,如果返回了 true 说明子节点已经匹配上了。但是如何传递祖先节点呢,可以采用闭包的方式。
确定方式后继续讨论,什么是祖先节点。
祖先节点有两种状态,一种是从左右两边汇聚上来的祖先节点。另一种是节点自身是祖先节点。
当返回值都是 true 的时候,毋庸置疑是第一种情况,那这时候 root 就是祖先节点,只需要保存 root 就可以了。
当返回值有一个是 true, 有一个是 false 的时候,这时候是两个节点的高度不一样,两种情况都有可能。这时候就要一直匹配 root 和提供的两个节点,什么时候匹配上,什么时候就确定祖先节点。返回值如果是 true 要进行穿透。
function lowestCommonAncestor(root, p, q) {
// p, q 以传参的方式性能会好
function dfs(root, p, q) {
if (!root) return false
// 后序遍历
const left = dfs(root.left, p, q)
const right = dfs(root.right, p, q)
if (left && right) {
ans = root
return true
}
if (left) {
// 因为可能是同一边的节点,所以都要比较
if (root.val === p.val || root.val === q.val) {
// 匹配上了说明自身是祖先节点
ans = root
}
return true // 穿透 left 的结果
}
if (right) {
if (root.val === p.val || root.val === q.val) {
// 匹配上了说明自身是祖先节点
ans = root
}
return true
}
// 子节点都没有匹配上,检查自己是否匹配
return root.val === p.val || root.val === q.val
}
let ans
dfs(root, p, q)
return ans
}
以上的代码可以进行简化,因为有相同的代码。
// 其他地方一样,dfs函数可以精简
function dfs(root, p, q) {
if (!root) return false
// 后序遍历
const left = dfs(root.left, p, q)
const right = dfs(root.right, p, q)
if (left && right || ((left || right) && (root.val === p.val || root.val === q.val)) {
ans = root
}
// 穿透结果或检查 root
return left || right || (root.val === q.val || root.val === p.val)
}