236.二叉树的最近公共祖先
236.1 题目
给定一个二叉树,找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 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 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
提示:
- 树中节点数目在范围
[2, 10^5]
内。 -10^9 <= Node.val <= 10^9
- 所有
Node.val
互不相同。 p != q
p
和q
均存在于给定的二叉树中。
236.2 题解
递归查找
// 方法一:递归查找
// 通过递归找到最近公共祖先
static TreeNode LowestCommonAncestor1(TreeNode root, TreeNode p, TreeNode q)
{
return LowestCommonAncestorRecursive(root, p, q);
}
// 递归函数,用于查找最近公共祖先
static TreeNode LowestCommonAncestorRecursive(TreeNode root, TreeNode p, TreeNode q)
{
// 如果当前节点为空或等于p或q,直接返回当前节点
if (root == null || root == p || root == q)
{
return root;
}
// 递归查找左子树和右子树
TreeNode left = LowestCommonAncestorRecursive(root.left, p, q);
TreeNode right = LowestCommonAncestorRecursive(root.right, p, q);
// 如果左子树和右子树都不为空,说明p和q分别位于当前节点的两侧,返回当前节点
if (left != null && right != null)
{
return root;
}
// 否则返回不为空的那个子树
return left ?? right;
}
带父指针的深度优先搜索
// 方法二:带父指针的深度优先搜索
// 使用哈希表存储每个节点的父节点,通过父节点查找最近公共祖先
static TreeNode LowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q)
{
// 哈希表存储每个节点的父节点
var parent = new Dictionary<TreeNode, TreeNode>();
// 栈用于深度优先搜索
var stack = new Stack<TreeNode>();
// 初始化:根节点没有父节点
parent[root] = null;
stack.Push(root);
// 直到找到p和q的父节点为止
while (!parent.ContainsKey(p) || !parent.ContainsKey(q))
{
var node = stack.Pop();
// 如果左子节点不为空,记录左子节点的父节点,并将左子节点入栈
if (node.left != null)
{
parent[node.left] = node;
stack.Push(node.left);
}
// 如果右子节点不为空,记录右子节点的父节点,并将右子节点入栈
if (node.right != null)
{
parent[node.right] = node;
stack.Push(node.right);
}
}
// 哈希集合存储p节点及其所有祖先
var ancestors = new HashSet<TreeNode>();
// 记录p节点及其所有祖先
while (p != null)
{
ancestors.Add(p);
p = parent[p];
}
// 找到q的最近公共祖先
while (!ancestors.Contains(q))
{
q = parent[q];
}
return q;
}
236.3 代码
using System;
using System.Collections.Generic;
public class TreeNode
{
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val = 0, TreeNode left = null, TreeNode right = null)
{
this.val = val;
this.left = left;
this.right = right;
}
}
class Program
{
static void Main()
{
#region 题目
// 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
// 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,
// 满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
// 示例 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 。因为根据定义最近公共祖先节点可以为节点本身。
// 示例 3:
// 输入:root = [1,2], p = 1, q = 2
// 输出:1
// 提示:
// 树中节点数目在范围 [2, 105] 内。
// -109 <= Node.val <= 109
// 所有 Node.val 互不相同 。
// p != q
// p 和 q 均存在于给定的二叉树中。
#endregion
#region 测试
// 示例 1
TreeNode root1 = new TreeNode(3,
new TreeNode(5, new TreeNode(6), new TreeNode(2, new TreeNode(7), new TreeNode(4))),
new TreeNode(1, new TreeNode(0), new TreeNode(8)));
TreeNode p1 = root1.left; // 5
TreeNode q1 = root1.right; // 1
TreeNode result1 = LowestCommonAncestor1(root1, p1, q1);
Console.WriteLine($"示例1 方法1 输出:{result1.val}");
TreeNode result1_2 = LowestCommonAncestor2(root1, p1, q1);
Console.WriteLine($"示例1 方法2 输出:{result1_2.val}");
// 示例 2
TreeNode root2 = new TreeNode(3,
new TreeNode(5, new TreeNode(6), new TreeNode(2, new TreeNode(7), new TreeNode(4))),
new TreeNode(1, new TreeNode(0), new TreeNode(8)));
TreeNode p2 = root2.left; // 5
TreeNode q2 = root2.left.right.right; // 4
TreeNode result2 = LowestCommonAncestor1(root2, p2, q2);
Console.WriteLine($"示例2 方法1 输出:{result2.val}");
TreeNode result2_2 = LowestCommonAncestor2(root2, p2, q2);
Console.WriteLine($"示例2 方法2 输出:{result2_2.val}");
// 示例 3
TreeNode root3 = new TreeNode(1, new TreeNode(2));
TreeNode p3 = root3; // 1
TreeNode q3 = root3.left; // 2
TreeNode result3 = LowestCommonAncestor1(root3, p3, q3);
Console.WriteLine($"示例3 方法1 输出:{result3.val}");
TreeNode result3_2 = LowestCommonAncestor2(root3, p3, q3);
Console.WriteLine($"示例3 方法2 输出:{result3_2.val}");
#endregion
}
#region 答案
// 方法一:递归查找
// 通过递归找到最近公共祖先
static TreeNode LowestCommonAncestor1(TreeNode root, TreeNode p, TreeNode q)
{
return LowestCommonAncestorRecursive(root, p, q);
}
// 递归函数,用于查找最近公共祖先
static TreeNode LowestCommonAncestorRecursive(TreeNode root, TreeNode p, TreeNode q)
{
// 如果当前节点为空或等于p或q,直接返回当前节点
if (root == null || root == p || root == q)
{
return root;
}
// 递归查找左子树和右子树
TreeNode left = LowestCommonAncestorRecursive(root.left, p, q);
TreeNode right = LowestCommonAncestorRecursive(root.right, p, q);
// 如果左子树和右子树都不为空,说明p和q分别位于当前节点的两侧,返回当前节点
if (left != null && right != null)
{
return root;
}
// 否则返回不为空的那个子树
return left ?? right;
}
// 方法二:带父指针的深度优先搜索
// 使用哈希表存储每个节点的父节点,通过父节点查找最近公共祖先
static TreeNode LowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q)
{
// 哈希表存储每个节点的父节点
var parent = new Dictionary<TreeNode, TreeNode>();
// 栈用于深度优先搜索
var stack = new Stack<TreeNode>();
// 初始化:根节点没有父节点
parent[root] = null;
stack.Push(root);
// 直到找到p和q的父节点为止
while (!parent.ContainsKey(p) || !parent.ContainsKey(q))
{
var node = stack.Pop();
// 如果左子节点不为空,记录左子节点的父节点,并将左子节点入栈
if (node.left != null)
{
parent[node.left] = node;
stack.Push(node.left);
}
// 如果右子节点不为空,记录右子节点的父节点,并将右子节点入栈
if (node.right != null)
{
parent[node.right] = node;
stack.Push(node.right);
}
}
// 哈希集合存储p节点及其所有祖先
var ancestors = new HashSet<TreeNode>();
// 记录p节点及其所有祖先
while (p != null)
{
ancestors.Add(p);
p = parent[p];
}
// 找到q的最近公共祖先
while (!ancestors.Contains(q))
{
q = parent[q];
}
return q;
}
#endregion
}
236.4 运行结果
示例1 方法1 输出:3
示例1 方法2 输出:3
示例2 方法1 输出:5
示例2 方法2 输出:5
示例3 方法1 输出:1
示例3 方法2 输出:1
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com