Was asked this in an interview recently and got stumped.
Given a binary tree where the nodes contain integer values, find the path (going all the way down to the leaves) that sum up to the lowest value.
So starting at root and traversing all the way down in a depth first fashion until you get to a leaf and adding up the node values along the way. Repeat for every possible path to the leaves.
I was just overwhelmed by the amount of possibilities there could be. But i tried doing a dfs, adding up values along the way, until i got to a leaf. Stored the path and the sum in a hashmap. But then I couldn't figure out how to reset the current sum and go down a different path to a different leaf the second time around.
It could be solved along these lines: Imagine that we define the function lowestValuePath(path, currentValue, bst) which takes a path as a string representation ("lrlrr" would be equivalent to having gone left, right, left, right, right), a value which is the currently accumulated sum of node values along this path, and bst which is the tree to traverse.
Our starting case would be lowestValuePath("", 0, rootNode), and termination would occur on a leaf, returning the path traversed and the value accumulated along this path.
Java-ish pseudo code could be:
TraverseResult {
String path;
int value;
}
TraverseResult lowestValuePath(path, currentValue, bst) {
val newValue = currentValue + bst.getNodeValue();
if bst.isLeaf():
return new TraverseResult(path, newValue);
else
val rightPath = lowestValuePath(path + "r", newValue, bst.getRightNode());
val leftPath = lowestValuePath(path + "l", newValue, bst.getLeftNode());
return leftPath < rightPath ? leftPath : rightPath
}
Some special case handling for the empty tree might be necessary..
If it is a "Binary Search Tree", then for every node T:
left(T) <= T <= right(T)
So actually just turn left all the time and you will get to the lowest value.
Related
So I'm currently reading Data Structures and Algorithms in Java Sixth Edition and one of the exercises in the book says:
Write algorithm that takes the binary tree T and returns an integer
value that represents the height of T. Recall that the height of a
binary tree is the number of edges between the tree's root and its
furthest leaf. For example, the following tree is of height 3:
The tree looks like this:
A
/ \
B C
/ \ \
D E F
\
G
Now if anyone can give some pointers on how I should start it that would be fantastic because at the moment I'm struggling
with this exercise.
Thank You so much in advance.
Edit: I apologise, I completely forgot to put my pseudocode, here it is:
Algorithm Total_Height (L_subtree, R_subtree, root)
Total_Height (root) {
If (root == null)
Return – 1
Return max (Total_Height( root-> L_subtree), Total_Height (root-> R_subtree))
}
Any remarks or improvements that can be made to this would be greatly appreciated.
To get the "height" of the tree, you have to keep track of the depth you are in right now:
int Tree_Height(root, depth) {
if (root == null) {
return -1;
}
return max(
Tree_Height(root.L_subtree, depth + 1),
Tree_Height(root.R_subtree, depth + 1),
depth
);
}
You need the depth in the max function because at the end of the tree the result will be -1, which will propagate to the top and the overall result from the function will be -1.
If you didn't understand this explanation, try to follow the execution of your program on a piece of paper. This is always helpful when fixing bugs in recursion.
Problem Statement: Given a circular linked list, implement an algoirthm that returns the node at the beginning of the loop.
The answer key gives a more complicated solution than what I propose. What's wrong with mine?:
public static Node loopDetection(Node n1) {
ArrayList<Node> nodeStorage = new ArrayList<Node>();
while (n1.next != null) {
nodeStorage.add(n1);
if (nodeStorage.contains(n1.next)) {
return n1;
}
else {
n1 = n1.next;
}
}
return null;
}
Your solution isO(n^2) time (each contains() in ArrayList is O(n) time) and O(n) space (for storing nodeStorage), while the "more complicated" solution is O(n) time and O(1) space.
The book offers the following solution, to whomever is interested, which is O(n) time and O(1) space:
If we move two pointers, one with speed 1 and another with speed 2,
they will end up meeting if the linked list has a loop. Why? Think
about two cars driving on a track—the faster car will always pass the
slower one! The tricky part here is finding the start of the loop.
Imagine, as an analogy, two people racing around a track, one running
twice as fast as the other. If they start off at the same place, when
will they next meet? They will next meet at the start of the next lap.
Now, let’s suppose Fast Runner had a head start of k meters on an n
step lap. When will they next meet? They will meet k meters before the
start of the next lap. (Why? Fast Runner would have made k + 2(n - k)
steps, including its head start, and Slow Runner would have made n - k
steps. Both will be k steps before the start of the loop.) Now, going
back to the problem, when Fast Runner (n2) and Slow Runner (n1) are
moving around our circular linked list, n2 will have a head start on
the loop when n1 enters. Specifically, it will have a head start of k,
where k is the number of nodes before the loop. Since n2 has a head
start of k nodes, n1 and n2 will meet k nodes before the start of the
loop. So, we now know the following:
1. Head is k nodes from LoopStart (by definition).
2. MeetingPoint for n1 and n2 is k nodes from LoopStart (as shown above). Thus, if we move n1 back to Head and keep n2 at MeetingPoint,
and move them both at the same pace, they will meet at LoopStart.
LinkedListNode FindBeginning(LinkedListNode head) {
LinkedListNode n1 = head;
LinkedListNode n2 = head;
// Find meeting point
while (n2.next != null) {
n1 = n1.next;
n2 = n2.next.next;
if (n1 == n2) {
break;
}
}
// Error check - there is no meeting point, and therefore no loop
if (n2.next == null) {
return null;
}
/* Move n1 to Head. Keep n2 at Meeting Point. Each are k steps
/* from the Loop Start. If they move at the same pace, they must
* meet at Loop Start. */
n1 = head;
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
// Now n2 points to the start of the loop.
return n2;
}
I had trouble visualizing what was going on with this algorithm. Hopefully this helps someone else.
At time t = k(3), p2 is twice the distance from the head(0) as p1, so for them to get back in line, we need p2 to 'catch up' to p1 and it will take L - k(8) 5 more steps to occur. p2 is travelling at 2x the speed of p1.
At time t = k + (L - k) (8), p2 needs to travel k steps forward to get back to k. If we reset p1 back to the head(0), we know that p1 and p2 will both meet back at k(3, 19 respectively) if p2 is travelling at the same speed as p1.
There is the solution given by amit. The problem is that you either know it or you don't, but you won't be able to figure it out in an interview. Since I have never had a need to find a cycle in a linked list, knowing it to me is pointless except for passing interviews. So for an interviewer, stating this as an interview question, and expecting amir's answer (which is nice because it has linear time and zero extra space), is quite stupid.
So your solution is mostly fine, except that you should use a hash table, and you must make sure that the hash table hashes references to nodes and not nodes. Say you have a node containing a string and a "next" pointer, and the hash function hashes the string and compares nodes as equal if the strings are equal. In that case you'd find the first node with a duplicate string, and not the node at the start of the loop, unless you are careful.
(amir's solution has a very similar problem in languages where == compares the objects, and not the references. For example in Swift, you'd have to use === (compares references) and not == (compares objects)).
If one binary tree has x nodes and the other has y nodes where x is bigger than y. I was thinking O(n2) because searching for each node is O(n).
And how about inserting then comparing the trees?
Assuming your binary trees are sorted, this is an O(n) operation (where n is the sum of the nodes in both trees, not the product).
You can simply run two "indexes" side by side through the trees stopping when an element is different. If you get to the end of both and no differences were found, then the trees were identical, something like the following pseudo-code:
def areEqual (tree1, tree2):
pos1 = first (tree1)
pos2 = first (tree2)
while pos1 != END and pos2 != END:
if tree1[pos1] != tree2[pos2]:
return false
pos1 = next (tree1, pos1)
pos2 = next (tree2, pos2)
if pos1 != END or pos2 != END:
return false
return true
If they're not sorted, and you have no other information that may allow you to optimise the function, and cannot use extra data structures, it will be O(n2), since you'll have to find an arbitrary equal node in the second tree for every single node in the first (as well as mark it somehow to indicate you've used it).
Keep in mind there are usually ways to trade space for time if the former is more important (and it often is).
For example, even with totally unordered trees, you can reduce the complexity considerably by using hashing for example:
def areEqual (tree1, tree2):
hash = []
# Add all items from first tree.
for item in tree1.allItems():
if not exists hash[item]
hash[item] = 0
hash[item] += 1
# Subtract all items from second tree.
for item in tree2.allItems():
if not exists hash[item]
hash[item] = 0
hash[item] -= 1
if hash[item] == 0:
delete hash[item]
if hash.size != 0:
return false
return true
Since hashing tends to amortise toward O(1), the problem as a whole can be considered O(n).
I have been trying to get this to work but while it works for majority of the input sometimes it gives the wrong output. I have spent some time debugging the code and it seems the problem is when i get a Node that is smaller than the root but bigger than the left node under the root.
How can I traverse the right sub-tree and still return the right key if no node in the right sub-tree is the floor node for that key?
Recall that if you do anything recursively, it can be transformed* into iteration.
Let's consider taking the floor of a well-formed BST, which should simply be the smallest element which is less than or equal to your key in the tree. All we have to do is traverse the tree to get it.
Let's implement it recursively so we can tease out a few important corollaries between iteration and recursion.
// Assuming non-null root node with method declaration
private Node floor(Node root, Key key, Node lowestNode) {
if(key.compareTo(root.getKey()) <= 0) {
if(root.getLeft() != null) {
return floor(root.getLeft(), key, lowestNode);
} else {
return root.compareTo(lowestNode) < 0 ? root : lowestNode;
}
} else {
if(root.getRight() != null) {
lowestRightNode.add(root);
return floor(root.getRight(), key, lowestNode);
} else {
return lowestNode;
}
}
Let's walk through the conditions for success.
If we compare a node to be less than or equal to our key value:
If we have a left child, there's something smaller. Traverse down the left half of the tree.
Otherwise, we're at the floor - which means we're at the node whose value is less than or equal to our key. Return it.
Otherwise (our node has a value greater than our key):
If we have a right child, there's a chance that our work isn't done yet (something's smaller). We'd like to keep it around since we could step off of the tree, so let's store it, then traverse down the right half of the tree.
Otherwise, we've fallen off of the tree. Return the smallest element we've kept track of.
An example may look something like this:
9
/ \
3 14
/ \
1 2
With a key of 12:
Compare with 9. We're larger. Store 9 in our lowest node variable, recurse right.
Compare with 14. We're smaller, but we don't have a left child. We compare the value 14 to 9 and 9 is smaller, so we return the node with 9.
If we want to convert this into iteration, then think about your starting point, your conditional check, and your incrementation steps.
Starting point: A non-null node
Conditional check:
key.compareTo(root.getKey()) <= 0
root.getLeft() != null
continue
root.compareTo(lowestRightNode) < 0 ? root : lowestRightNode
terminal
else
root.getRight() != null
store temp value and continue
return lowestRightNode
terminal
Pay close attention to your continuation conditions, and what other work you'd have to do to keep track of the lowest node you've seen so far (only for the right-hand side, that is).
*: Some recursive operations are more painful to convert than others, of course.
i have implemented a function to find the depth of a node in a binary search tree but my implementation does not take care of duplicates. I have my code below and would like some suggestions on how to consider duplicates case in this function. WOuld really appreciate your help.
public int depth(Node n) {
int result=0;
if(n == null || n == getRoot())
return 0;
return (result = depth(getRoot(), n, result));
}
public int depth(Node temp, Node n, int result) {
int cmp = n.getData().compareTo(temp.getData());
if(cmp == 0) {
int x = result;
return x;
}
else if(cmp < 0) {
return depth(temp.getLeftChild(), n, ++result);
}
else {
return depth(temp.getRightChild(), n, ++result);
}
}
In the code you show, there is no way to prefer one node with same value over another. You need to have some criteria for differentiation.
You can retrieve the list of all duplicate nodes depths using the following approach, for example:
Find the depth of your node.
Find depth of the same node for the left subtree emerging from the found node - stop if not found.
Add depth of the previously found node (in 1) to the depth of the duplicate
Find depth of the same node for the right subtree emerging from the found node (in 1) - stop if not found.
Add depth of the previously found node (in 1) to the depth of the duplicate
Repeat for left and right subtrees.
Also see here: What's the case for duplications in BST?
Well, if there's duplicates, then the depth of a node with a given value doesn't make any sense on its own, because there may be multiple nodes with that value, hence multiple depths.
You have to decide what it means, which could be (not necessarily an exhaustive list):
the depth of the deepest node with that value.
the depth of the shallowest node with that value.
the depth of the first node found with that value.
the average depth of all nodes with that value.
the range (min/max) of depths of all nodes with that value.
a list of depths of all nodes with that value.
an error code indicating your query made little sense.
Any of those could make sense in specific circumstances.
Of course, if n is an actual pointer to a node, you shouldn't be comparing values of nodes at all, you should be comparing pointers. That way, you will only ever find one match and the depth of it makes sense.
Something like the following pseudo-code should do:
def getDepth (Node needle, Node haystack, int value):
// Gone beyond leaf, it's not in tree
if haystack == NULL: return -1
// Pointers equal, you've found it.
if needle == haystack: return value
// Data not equal search either left or right subtree.
if needle.data < haystack.data:
return getDepth (needle, haystack.left, value + 1)
if needle.data > haystack.data:
return getDepth (needle, haystack.right, value + 1)
// Data equal, need to search BOTH subtrees.
tryDepth = getDepth (needle, haystack.left, value + 1)
if trydepth == -1:
tryDepth = getDepth (needle, haystack.right, value + 1)
return trydepth
The reason why you have to search both subtrees when the values are equal is because the desired node may be in either subtree. Where the values are unequal, you know which subtree it's in. So, for the case where they're equal, you check one subtree and, if not found, you check the other.