这是一个受欢迎的访谈问题,我可以在这个主题上找到的唯一一篇文章来自TopCoder。对我来说不幸的是,从采访答案的角度来看,它看起来过于复杂。
除了绘制两个节点的路径并推断祖先之外,是否有更简单的方法可以做到这一点? (这是一个很受欢迎的答案,但是面试问题有一个变化,需要一个恒定的空间答案)。
答案 0 :(得分:11)
一个简单的(但更少涉及的版本)可能只是(这里的Java家伙有点生疏,所以请原谅语法,但我认为你不必调整太多)。这就是我扔在一起的东西。
class Program
{
static void Main(string[] args)
{
Node node1 = new Node { Number = 1 };
Node node2 = new Node { Number = 2, Parent = node1 };
Node node3 = new Node { Number = 3, Parent = node1 };
Node node4 = new Node { Number = 4, Parent = node1 };
Node node5 = new Node { Number = 5, Parent = node3 };
Node node6 = new Node { Number = 6, Parent = node3 };
Node node7 = new Node { Number = 7, Parent = node3 };
Node node8 = new Node { Number = 8, Parent = node6 };
Node node9 = new Node { Number = 9, Parent = node6 };
Node node10 = new Node { Number = 10, Parent = node7 };
Node node11 = new Node { Number = 11, Parent = node7 };
Node node12 = new Node { Number = 12, Parent = node10 };
Node node13 = new Node { Number = 13, Parent = node10 };
Node commonAncestor = FindLowestCommonAncestor(node9, node12);
Console.WriteLine(commonAncestor.Number);
Console.ReadLine();
}
public class Node
{
public int Number { get; set; }
public Node Parent { get; set; }
public int CalculateNodeHeight()
{
return CalculateNodeHeight(this);
}
private int CalculateNodeHeight(Node node)
{
if (node.Parent == null)
{
return 1;
}
return CalculateNodeHeight(node.Parent) + 1;
}
}
public static Node FindLowestCommonAncestor(Node node1, Node node2)
{
int nodeLevel1 = node1.CalculateNodeHeight();
int nodeLevel2 = node2.CalculateNodeHeight();
while (nodeLevel1 > 0 && nodeLevel2 > 0)
{
if (nodeLevel1 > nodeLevel2)
{
node1 = node1.Parent;
nodeLevel1--;
}
else if (nodeLevel2 > nodeLevel1)
{
node2 = node2.Parent;
nodeLevel2--;
}
else
{
if (node1 == node2)
{
return node1;
}
node1 = node1.Parent;
node2 = node2.Parent;
nodeLevel1--;
nodeLevel2--;
}
}
return null;
}
}
答案 1 :(得分:2)
恒定空间答案:(虽然不一定有效)。
有一个函数findItemInPath(int index,int searchId,Node root)
然后从0深度的树迭代,找到两个搜索路径中的第0个项目,第1个项目等。
当你发现i使得函数为两者返回相同的结果时,而不是i + 1, 然后路径中的第i个项目是最低共同祖先。
答案 2 :(得分:2)
文章解决方案更复杂的主要原因是它处理两阶段问题 - 预处理然后查询 - 而你的问题听起来好像你只做一个查询所以预处理没有做感。它还处理任意树而不是二叉树。
最佳答案肯定取决于树的细节。对于许多种树,时间复杂度将是O(h),其中h是树的高度。如果你有指向父节点的指针,那么简单的“恒定空间”答案就像在Mirko的解决方案中一样,找到两个节点的高度并比较相同高度的祖先。请注意,这适用于具有父链接,二进制或否的任何树。我们可以通过迭代高度函数并从主循环中分离“获取相同深度”循环来改进Mirko的解决方案:
int height(Node n){
int h=-1;
while(n!=null){h++;n=n.parent;}
return h;
}
Node LCA(Node n1, Node n2){
int discrepancy=height(n1)-height(n2);
while(discrepancy>0) {n1=n1.parent;discrepancy--;}
while(discrepancy<0) {n2=n2.parent;discrepancy++;}
while(n1!=n2){n1=n1.parent();n2=n2.parent();}
return n1;
}
“恒定空间”周围的引号是因为通常我们需要O(log(h))空间来存储它们之间的高度和差异(例如,3个BigIntegers)。但是如果你处理的树木高度太大而不能长时间存在,那么你可能还有其他问题需要担心,这比存储几个节点的高度更为紧迫。
如果您有BST,那么您可以轻松地采用共同的祖先(usu。从root开始)并检查其子项以查看它们中的任何一个是否是共同的祖先:
Node LCA(Node n1, Node n2, Node CA){
while(true){
if(n1.val<CA.val & n2.val<CA.val) CA=CA.left;
else if (n1.val>CA.val & n2.val>CA.val) CA=CA.right;
else return CA;
}
}
正如Philip JF所提到的,同样的想法可以在任何树中用于恒定空间算法,但是对于一般树来说这样做会很慢,因为反复计算出CA.left或CA.right是否是一个共同的祖先会重复很多工作,所以你通常更喜欢使用更多的空间来节省一些时间。做出这种权衡的主要方法基本上就是你提到的算法(从根存储路径)。
答案 3 :(得分:1)
重要的是你正在使用什么样的树。您始终可以判断一个节点是否是恒定空间中另一个节点的祖先,并且顶部节点始终是一个共同的祖先,因此将最低公共祖先放在恒定空间中只需要向下迭代。在二叉搜索树上,这很容易快速完成,但它可以在任何树上工作。
许多不同的权衡取决于这个问题,树的类型很重要。如果您有指向父节点的指针,而不仅仅是对子节点的指针(Mirko的代码使用它),问题往往会容易得多。
答案 4 :(得分:1)
使用log(n)空间的明显解决方案(n是节点数)是您提到的算法。这是一个实现。在最坏的情况下,它花费O(n)时间(假设您正在搜索共同祖先的节点之一包括最后一个节点)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication2
{
class Node
{
private static int counter = 0;
private Node left = null;
private Node right = null;
public int id = counter++;
static Node constructTreeAux(int depth)
{
if (depth == 0)
return null;
Node newNode = new Node();
newNode.left = constructTree(depth - 1);
newNode.right = constructTree(depth - 1);
return newNode;
}
public static Node constructTree(int depth)
{
if (depth == 0)
return null;
Node root = new Node();
root.left = constructTreeAux(depth - 1);
root.right = constructTreeAux(depth - 1);
return root;
}
private List<Node> findPathAux(List<Node> pathSoFar, int searchId)
{
if (this.id == searchId)
{
if (pathSoFar == null)
pathSoFar = new List<Node>();
pathSoFar.Add(this);
return pathSoFar;
}
if (left != null)
{
List<Node> result = left.findPathAux(null, searchId);
if (result != null)
{
result.Add(this);
return result;
}
}
if (right != null)
{
List<Node> result = right.findPathAux(null, searchId);
if (result != null)
{
result.Add(this);
return result;
}
}
return null;
}
public static void printPath(List<Node> path)
{
if (path == null)
{
Console.Out.WriteLine(" empty path ");
return;
}
Console.Out.Write("[");
for (int i = 0; i < path.Count; i++)
Console.Out.Write(path[i] + " ");
Console.Out.WriteLine("]");
}
public override string ToString()
{
return id.ToString();
}
/// <summary>
/// Returns null if no common ancestor, the lowest common ancestor otherwise.
/// </summary>
public Node findCommonAncestor(int id1, int id2)
{
List<Node> path1 = findPathAux(null, id1);
if (path1 == null)
return null;
path1 = path1.Reverse<Node>().ToList<Node>();
List<Node> path2 = findPathAux(null, id2);
if (path2 == null)
return null;
path2 = path2.Reverse<Node>().ToList<Node>();
Node commonAncestor = this;
int n = path1.Count < path2.Count? path1.Count : path2.Count;
printPath(path1);
printPath(path2);
for (int i = 0; i < n; i++)
{
if (path1[i].id == path2[i].id)
commonAncestor = path1[i];
else
return commonAncestor;
}
return commonAncestor;
}
private void printTreeAux(int depth)
{
for (int i = 0; i < depth; i++)
Console.Write(" ");
Console.WriteLine(id);
if (left != null)
left.printTreeAux(depth + 1);
if (right != null)
right.printTreeAux(depth + 1);
}
public void printTree()
{
printTreeAux(0);
}
public static void testAux(out Node root, out Node commonAncestor, out int id1, out int id2)
{
Random gen = new Random();
int startid = counter;
root = constructTree(5);
int endid = counter;
int offset = gen.Next(endid - startid);
id1 = startid + offset;
offset = gen.Next(endid - startid);
id2 = startid + offset;
commonAncestor = root.findCommonAncestor(id1, id2);
}
public static void test1()
{
Node root = null, commonAncestor = null;
int id1 = 0, id2 = 0;
testAux(out root, out commonAncestor, out id1, out id2);
root.printTree();
commonAncestor = root.findCommonAncestor(id1, id2);
if (commonAncestor == null)
Console.WriteLine("Couldn't find common ancestor for " + id1 + " and " + id2);
else
Console.WriteLine("Common ancestor for " + id1 + " and " + id2 + " is " + commonAncestor.id);
}
}
}