【问题标题】:Lowest Common Ancestor of a Binary Tree二叉树的最低共同祖先
【发布时间】:2011-07-28 21:54:12
【问题描述】:

这是一个流行的面试问题,我能找到的唯一一篇关于该主题的文章来自TopCoder。对我来说不幸的是,从面试答案的角度来看,它看起来过于复杂。

除了绘制到两个节点的路径并推断出祖先之外,没有更简单的方法吗? (这是一个流行的答案,但面试问题的一个变体要求一个恒定的空格答案)。

【问题讨论】:

    标签: java binary-tree


    【解决方案1】:

    一个简单的(但涉及的版本少得多)可能只是(这里的.NET 家伙 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;
        }
    }
    

    【讨论】:

    • 感谢 Mirko,但拥有父指针会使问题变得微不足道。我的不好 - 我忘了在问题中提到这一点。非常感谢您的解决方案:)
    • 非常好的解决方案......虽然使用父指针......不明白为什么它不被接受!
    【解决方案2】:

    使用什么样的树很重要。您总是可以判断一个节点是否是恒定空间中另一个节点的祖先,并且顶部节点始终是共同祖先,因此在恒定空间中获取最低公共祖先只需要向下迭代。在二叉搜索树上,这很容易快速完成,但它适用于任何树。

    许多不同的权衡与此问题相关,树的类型很重要。如果您有指向父节点的指针,而不仅仅是指向子节点的指针,问题往往会容易得多(Mirko 的代码使用了这个)

    另请参阅: http://en.wikipedia.org/wiki/Lowest_common_ancestor

    【讨论】:

      【解决方案3】:

      常数空间答案:(虽然不一定有效)。

      有一个函数 findItemInPath(int index, int searchId, Node root)

      然后从树的 0 .. 深度迭代,在两个搜索路径中找到第 0 项、第 1 项等。

      当你发现 i 使得函数对两者都返回相同的结果,但不是 i+1 时, 那么路径中的第 i 个项目是最低的共同祖先。

      【讨论】:

      • 谢谢,我认为这是个好主意。我们可能还需要在函数中添加另一个参数,说明每一步的父节点是什么,这样我们就可以在第 (i+1) 次调用中发现不同的结果时立即打印它。听起来不错 - 再次感谢!
      • 我认为这种方法仅作为智力练习有用,因为除非排序和平衡,否则时间是 O(N),在这种情况下是 O(log(n)) 时间。但是自然算法使用 O(log(N)) 空间,并且将是对下面代码的简单修改,以根据键的值(选择左子树或右子树)搜索树,而不仅仅是前缀顺序(搜索左然后右)。
      【解决方案4】:

      使用 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);
              }
          }
      }
      

      【讨论】:

      • 谢谢,我选择了您的其他解决方案,因为它使用恒定空间。
      • 它可能会使用常量空间,但我认为在常量空间中很难有效地实现。例如,如何找到 id 路径上的第一个元素?答案是搜索左子树或右子树,然后选择合适的子树。但是搜索子树需要 O(N) 时间,所以这将是大约 O(N*2) 时间。
      • 我认为关于恒定空间答案的问题是一个技巧问题。你应该说“是的,有一个恒定的空间答案(在上面描述我的答案),但这不切实际,因为它需要 O(N*2) 时间,所以最好使用上面的解决方案 O(N) 和通过对树进行平衡和排序,可以轻松地将其改进为 O(log(N))。
      【解决方案5】:

      文章的解决方案更复杂的主要原因是它正在处理一个两阶段的问题 - 预处理然后查询 - 而从你的问题听起来你只做一个查询所以预处理不会感觉。它还处理任意树而不是二叉树。

      最佳答案当然取决于树的详细信息。对于多种树,时间复杂度将是 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,那么您可以轻松地获取一个共同祖先(通常以 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 是一个共同的祖先会重复很多工作,所以你通常更喜欢使用更多的空间来节省一些时间。进行这种权衡的主要方法基本上是您提到的算法(从根存储路径)。

      【讨论】:

      • 谢谢 - 是的,我根本不想针对这种特殊情况进行预处理。
      【解决方案6】:

      here 描述的自底向上方法是 O(n) 时间,O(1) 空间方法:

      http://www.leetcode.com/2011/07/lowest-common-ancestor-of-a-binary-tree-part-i.html

      Node *LCA(Node *root, Node *p, Node *q) {
        if (!root) return NULL;
        if (root == p || root == q) return root;
        Node *L = LCA(root->left, p, q);
        Node *R = LCA(root->right, p, q);
        if (L && R) return root;  // if p and q are on both sides
        return L ? L : R;  // either one of p,q is on one side OR p,q is not in L&R subtrees
      }
      

      【讨论】:

        猜你喜欢
        • 2012-11-08
        • 1970-01-01
        • 2018-03-09
        • 2012-01-16
        • 2020-08-16
        • 2017-04-04
        • 2017-02-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多