简体   繁体   中英

Why is the runtime of this algorithm O(t)?

This is from problem 4.8 of Cracking the Coding Interview 6th edition. The following code is one solution to the following prompt: "Find the first common ancestor of two nodes in a binary tree"

TreeNode commonAncestor(TreeNode root, TreeNode p, TreeNode q){
/* Checks if either node is not in the tree, or if one covers the other. */
   if(!covers(root, p) || !covers(root, q)){
      return null;
   } else if (covers(p,q)){
      return p;
   } else if (cover(q,p)){
      return q;
   }

   /* Traverse upwards until you find a node that covers q. */
   TreeNode sibling = getSibling(p);
   TreeNode parent = p.parent;
   while(!covers(sibling, q)){
    sibling = getSibling(parent);
    parent = parent.parent;
   }

   return parent;
}

boolean covers(TreeNode root, TreeNode p){
   if(node == null) return false;
   if(root == p) return true;
   return covers(root.left, p) || covers(root.right,p);
}

TreeNode getSibling(TreeNode node){
   if(node == null || node.parent ==null){
     return null;
   }

   TreeNode parent = node.parent;
   return parent.left == node ? parent.right: parent.left;

}

The book says that "this algorithm takes O(t) time, where t is the size of the subtree for the first common ancestor. In the worst case this will be O(n)"

However, aren't the calls to covers from root at the beginning of commonAncestor making the runtime O(d+t), d being the depth of the either p or q, depending on which one is deeper.

Well, it looks like cover(root,p) will search the entire sub-tree rooted at x , since it checks both root.left and root.right recursively.

But yes, this problem can be solved in time O(d). (Go up from each of p , q to the root, and then just the first element the two lists have in common.)

Um, it looks like that code inside cover is wrong, too, in a couple different ways:

  • it presumably wants to return false rather than return null when we've recurred "off the end" of a branch;
  • even more troublesome, it should check that occasionally it has found the target: if (root==p) return true . (One could be clever and modify the existing return statement to be return (root==p) || covers(..,..) || covers(..,..) .)

You may want to review the part on Big O in the beginning of the book. O(d+t) is somewhat accurate, but because that's a plus one of them (either d or t) is going to get bigger faster than the other. In this case, t = # of nodes in a subtree, and d = depth in the overall tree. T is going to grow significantly faster than d.

As an illustration:

      1
    /   \
  2       3
 / \     / \
4   5   6   7

If We're looking at this tree and we want to know the common ancestor for 4 and 5:
  t = 3
  d = 3
if we want the common ancestor of 2 and 7 in the same tree then:
  t = 7
  d = 3

Due to the way trees work depth will always be equal to or smaller than the number of nodes. Therefore the time complexity will be average (big theta?) of t (# of nodes in subtree) and at worst (big o) n (#number of nodes in tree).

As an aside, the checks to root are going to do O(n) at the start which would make the whole thing O(n), but the author states that it does, in fact have O(n). "this algorithm takes O(t) time" is, I think, the author's analysis for average case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM