Lowest Common Ancestor of a Binary Tree

后端 未结 6 443
栀梦
栀梦 2021-02-02 00:01

This is a popular interview question and the only article I can find on the topic is one from TopCoder. Unfortunately for me, it looks overly complicated from an interview answe

相关标签:
6条回答
  • A simplistic (but much less involved version) could simply be (.NET guy here Java a bit rusty, so please excuse the syntax, but I think you won't have to adjust too much). This is what I threw together.

    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;
        }
    }
    
    0 讨论(0)
  • 2021-02-02 00:53

    It matters what kind of tree you are using. You can always tell if a node is the ancestor of another node in constant space, and the top node is always a common ancestor, so getting the Lowest Common Ancestor in constant space just requires iterating your way down. On a binary search tree this is pretty easy to do fast, but it will work on any tree.

    Many different trade offs are relevant for this problem, and the type of tree matters. The problem tends is much easier if you have pointers to parent nodes, and not just to children (Mirko's code uses this)

    See also: http://en.wikipedia.org/wiki/Lowest_common_ancestor

    0 讨论(0)
  • 2021-02-02 00:56

    The main reason why the article's solutions are more complicated is that it is dealing with a two-stage problem- preprocessing and then queries- while from your question it sounds like you're only doing one query so preprocessing doesn't make sense. It's also dealing with arbitrary trees rather than binary trees.

    The best answer will certainly depend on details about the tree. For many kinds of trees, the time complexity is going to be O(h) where h is the tree's height. If you've got pointers to parent nodes, then the easy "constant-space" answer is, as in Mirko's solution, to find both nodes' height and compare ancestors of the same height. Note that this works for any tree with parent links, binary or no. We can improve on Mirko's solution by making the height function iterative and by separating the "get to the same depth" loops from the main loop:

    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;
    }

    The quotation marks around "constant-space" are because in general we need O(log(h)) space to store the heights and the difference between them (say, 3 BigIntegers). But if you're dealing with trees with heights too large to stuff in a long, you likely have other problems to worry about that are more pressing than storing a couple nodes' heights.

    If you have a BST, then you can easily take a common ancestor (usu. starting with root) and check its children to see whether either of them is a common ancestor:

    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;
     }
    }

    As Philip JF mentioned, this same idea can be used in any tree for a constant-space algorithm, but for a general tree doing it this way will be really slow since figuring out repeatedly whether CA.left or CA.right is a common ancestor will repeat a lot of work, so you'd normally prefer to use more space to save some time. The main way to make that tradeoff would be basically the algorithm you've mentioned (storing the path from root).

    0 讨论(0)
  • 2021-02-02 00:58

    The obvious solution, that uses log(n) space, (n is the number of nodes) is the algorithm you mentioned. Here's an implementation. In the worst case it takes O(n) time (imagine that one of the node you are searching common ancestor for includes the last node).

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-02 01:02

    Constant space answer: (although not necessarily efficient).

    Have a function findItemInPath(int index, int searchId, Node root)

    then iterate from 0 .. depth of tree, finding the 0-th item, 1-th item etc. in both search paths.

    When you find i such that the function returns the same result for both, but not for i+1, then the i-th item in the path is the lowest common ancestor.

    0 讨论(0)
  • 2021-02-02 01:08

    The bottom up approach described here is an O(n) time, O(1) space approach:

    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
    }
    
    0 讨论(0)
提交回复
热议问题