大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。
题目
面试题 04.08. 首个共同祖先
设计并实现一个算法,找出二叉树中某两个节点的第一个共同祖先。不得将其他的节点存储在另外的数据结构中。注意:这不一定是二叉搜索树。
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
思路
- 递归找到
p和q节点,并记录它们的所有祖先节点; - 找到
p节点的所有祖先节点,用set记录下来; - 用队列存储
q节点的所有祖先节点,一个个取出来判断是否存在set中; - 最终它们总能找到公共祖先节点,因为树中只有一个根节点,所以不需要做特殊处理。
实现
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(root, p, q) {
let set = new Set();
let queue = [];
// 寻找P节点,把所有的祖先构建成Set对象
function findPNode(node, p) {
if (!node) return false;
if (node === p || findPNode(node.left, p) || findPNode(node.right, p)) {
set.add(node);
return true;
}
return false;
}
// 寻找Q节点,把所有的祖先构建成队列
function findQNode(node, q) {
if (!node) return false;
if (node === q || findQNode(node.left, q) || findQNode(node.right, q)) {
queue.push(node);
return true;
}
return false;
}
// 找到P节点的所有祖先,记录下来
findPNode(root, p);
// 找到Q节点的所有祖先
findQNode(root, q);
// 找交集
while (queue.length) {
const cur = queue.shift();
if (set.has(cur)) {
return cur;
}
}
};
优化
以上两个遍历的过程,我们可以合并成同一个,我们只需要用两个变量来分别记录p和q节点分别是否已经被找到了即可。
-
记录
p和q节点分别是否已经被找到; -
遍历左子树寻找节点,如果找到了就记录下来;
-
遍历完左子树,可能有三种情况:
3.1 同时找到了两个节点,那么直接返回最后一级根节点即可;
3.2 两个节点都不存在,那么它们必定在右子树上;
3.3 找到了一个节点,那么遍历完右子树还是要往上查找。
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(root, p, q) {
let hasP = false, hasQ = false;
function dfs(node, p, q) {
if (!node) return null;
// 遍历左子树
let leftResult = dfs(node.left, p, q);
if (hasP && hasQ) return leftResult;
// 如果只有一个在左子树上,说明要往上查找
let hasOne = hasP || hasQ;
// 遍历右子树
let rightResult = dfs(node.right, p, q);
if (hasP && hasQ) return hasOne ? node : rightResult;
if (node.val === p.val) hasP = true;
if (node.val === q.val) hasQ = true;
return hasP && hasQ ? node : null;
}
return dfs(root, p, q);
};
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。