Reputation: 11450
In Cracking the Coding Interview 6th Edition there's a question (4.4) where you're suppose to find out if a binary tree is balanced, where balanced in this case means if any side is deeper than the other by more than 1.
I solved this recursively like this:
def isBalanced(root):
return abs(getDepth(root.left) - getDepth(root.right)) > 1
def getDepth(node):
if node is None:
return 0
return 1 + max([getDepth(node.left), getDepth(node.right)])
So to walk through it. It recursively check each side of each node and pass it up to the root, if the root then have a higher difference between the left and right subtrees than 1, it returns False, else True.
In the answer section of the book the author writes the following about this type of solution:
Although this works, it's not very efficient. On each node, we recurse through it's entire subtree. This means that getHeight is called repeatedly on the same nodes. The algorithm is O(N log N) since each node is "touched" once per node above it.
The books solution is the following:
int getHeight(TreeNode root) {
if (root == null) return -1;
return Math.max(getHeight(root.left), getHeight(root.right)) + 1;
}
boolean isBalanced(TreeNode root) {
if (root == null) return true;
int heightDiff = getHeight(root.left) - getHeight(root.right);
if (Math.abs(heightDiff) < 1) {
return false;
} else {
return isBalanced(root.left) && isBalanced(root.right);
}
}
Unfortunately I can't understand how this is the case. In my mind, the algorithm only checks the level below it. Each node is not checked multiple times. When I look at this algorithm it is O(N).
Can someone help me confirm if I understood the Big-O of this algorithm correctly, or if I missed something?
Upvotes: 4
Views: 842
Reputation: 18838
Your algorithm time complexity is T(n) = 2T(n/2) + 1
. Hence, time complexity of your algorithm would be T(n) = O(n)
. However, the correctness of your algorithm is under question.
Upvotes: 0
Reputation: 15035
Let's write the time complexity function of isBalanced
as T(n)
. The average-case recurrence relation is therefore:
Where the O(n)
comes from the two calls to getHeight
, which we know to be O(n)
. Therefore, using the Master theorem, the overall complexity of isBalanced
is O(n log n)
.
Your solution does not call isBalanced
on the child nodes, which means the O(n)
in the relation above is replaced by an O(1)
, giving O(n)
overall (again from the Master theorem). It does not however (as an obvious consequence!) check that the child nodes are balanced, so is incorrect.
The problem with CTCI's naive solution is that it effectively calls getHeight
again for each child node (by calling isBalanced
), which is unnecessary. One can incorporate the balance-checking functionality into getHeight
to obtain a solution in O(n)
:
int balance_internal(TreeNode root)
{
// change the specification slightly to return 0 for a leaf
// ... and -1 for an unbalanced node
if (root == null) return 0;
int left_h = balance_internal(root.left);
int right_h = balance_internal(root.right);
// if either node is unbalanced
if (left_h == -1 || right_h == -1)
return -1;
// return max height as before
// ... but now with the knowledge that both child nodes are balanced
return Math.abs(left_h - right_h) > 1 ? -1 : 1 + Math.max(left_h, right_h);
}
boolean isBalanced(TreeNode root)
{
return (balance_internal(root) > -1);
}
Although perhaps not as graceful as the provided solution, this does not create duplicate calls to child nodes, and instead reuses the results from the first set of calls. The recurrence relation is thus the same as that of your own solution, giving O(n)
.
Upvotes: 8