废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【二叉树的节点查找】,使用【二叉树】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为:目标公司+最近一年+出现频率排序,由高到低的去牛客TOP101去找,只有两个地方都出现过才做这道题(CodeTop本身汇聚了LeetCode的来源),确保刷的题都是高频要面试考的题。
名曲目标题后,附上题目链接,后期可以依据解题思路反复快速练习,题目按照题干的基本数据结构分类,且每个分类的第一篇必定是对基础数据结构的介绍。
二叉树的最近公共祖先【MID】
最近公共祖先(Lowest Common Ancestor, LCA)是指在一个二叉树中,两个节点p和q的最低公共祖先节点,即深度最深且同时是p和q的祖先的节点,同时节点本身也可以为其最近公共祖先
题干
解题思路
根据以上定义,若 root 是 p,q的 最近公共祖先 ,则只可能为以下情况之一:
- p 和 q 在 root 的子树中,且分列 root 的 异侧(即分别在左、右子树中);
- p=root ,且 q 在 roo的左或右子树中;
- q=root,且 p 在 root的左或右子树中;
- 终止条件:当越过叶节点,则直接返回 null;当 root 等于 p,q,则直接返回 root;
- 递推工作: 开启递归左子节点,返回值记为 left;开启递归右子节点,返回值记为 right;
- 返回值: 根据 left和 right,可展开为四种情况;
- 当 left 和 righ同时为空 :说明一直遍历到叶节点, root 的左 / 右子树中都不包含 p,q ,返回 null;
- 当 left和 right 同时不为空 :说明 pq 分列在 roo的 异侧 (分别在 左 / 右子树),因此 root为最近公共祖先,返回 root
- 当 left为空 ,right不为空 :pq 都不在 root 的左子树中,直接返回 right。具体可分为两种情况:pq 其中一个在 root 的 右子树 中,此时 right指向 p(假设为 p );pq 两节点都在 root的 右子树 中,此时的 right指向 最近公共祖先节点 ;
- 当 left不为空 , right为空 :与情况 3. 同理
代码实现
给出代码实现基本档案
基本数据结构:二叉树
辅助数据结构:无
算法:递归、DFS
技巧:无
其中数据结构、算法和技巧分别来自:
- 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
- 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
- 技巧:双指针、滑动窗口、中心扩散
当然包括但不限于以上
public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
return dfsFindRoot(root, p, q);
}
private TreeNode dfsFindRoot(TreeNode node, TreeNode p, TreeNode q) {
// 1 递归终止条件:越过叶子节点没找到;root为p或root为q
if (node == null || node == p || node == q) {
return node;
}
// 2 本级任务:分别在左右子树中寻找节点
TreeNode left = dfsFindRoot(node.left, p, q);
TreeNode right = dfsFindRoot(node.right, p, q);
// 3 本级任务:分别在左右子树中寻找节点
if (left != null && right != null) {
//如果两个节点分别存在于左右子树,则当前节点即是最近公共祖先
return node;
}
// 如果两个节点在root的同侧,则返回的节点即为公共节点
if (right == null && left != null) {
return left;
}
if (right != null && left == null) {
return right;
}
// 如果两个节点左右都没有找到,则返回null
return null;
}
复杂度分析
时间复杂度:O(n),其中n为二叉树的节点数,遍历整棵二叉树
空间复杂度:O(n),最坏情况下,二叉树化为链表,递归栈深度最大为n
拓展知识:二叉树的最近公共祖先
最近公共祖先(Lowest Common Ancestor, LCA)是指在一个二叉树中,两个节点p和q的最低公共祖先节点,即深度最深且同时是p和q的祖先的节点。以下是一种基于递归的实现思路,并提供Java的实现代码:
定义
假设有一个二叉树,每个节点包含如下信息:
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
实现思路
- 从根节点开始遍历二叉树。
- 对于当前节点,递归地查找其左子树和右子树,看看是否能找到节点p和节点q。
- 如果左子树中找到了节点p或节点q,或者右子树中找到了节点p或节点q,那么当前节点就是它们的最近公共祖先。
- 如果左子树和右子树都找到了最近公共祖先,那么当前节点就是它们的最近公共祖先。
- 最后,返回最近公共祖先。
Java实现代码
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// Base case: 如果当前节点为空或者等于p或q中的任意一个,直接返回当前节点
if (root == null || root == p || root == q) {
return root;
}
// 递归查找左子树和右子树
TreeNode leftLCA = lowestCommonAncestor(root.left, p, q);
TreeNode rightLCA = lowestCommonAncestor(root.right, p, q);
// 如果左子树和右子树都找到了最近公共祖先,那么当前节点就是最近公共祖先
if (leftLCA != null && rightLCA != null) {
return root;
}
// 如果只在一侧找到了最近公共祖先,返回那一侧找到的结果
return (leftLCA != null) ? leftLCA : rightLCA;
}
这段代码会在二叉树中查找节点p和q的最近公共祖先,并返回该节点。如果一个节点是另一个节点的祖先,那么它自己也可以是自己的祖先,因此在递归中会检查当前节点是否等于p或q中的任意一个,如果是就返回当前节点。否则,递归地查找左子树和右子树,并根据左右子树的结果确定最近公共祖先。