Walmart Labs Interview Question
Software Engineer / DevelopersCountry: United States
Interview Type: Phone Interview
Consider a particular "node":- we have three cases
1. Longest Possible path includes node->left and not this node
2. Longest Possible path includes node->right and not his node
3. Longest Possible path includes this "node"
Thus, longest possible path should be maximum of these three: (node->left, node->right, 1+height(node->left) + height(node->right))
Do a BFS from a randomly chosen node, and find the farthest leaf. Then do the BFS from that farthest node to find the farthest node again. This two nodes are the answer :)
Normally each node of a tree holds references either to its children or to it's parent. I'm wondering with any one of the structure of a node how you would perform two BFS's!!
If you assume each node knows it's children, then performing 1st BFS is easy and takes O(n) time where n=number of nodes. However once you get a leaf, how do you perform another BFS considering this leaf as root !!
Am I missing something!!
int FindDia(NODE root,int *length , int *NullNodeValue , int *LeftNullNode , int *RightNullNode)
{
int a,b;
int left, right;
if(root == NULL)
return 0;
a=FindDia(root->left,length,&left,LeftNullNode,RightNullNode);
b=FindDia(root->right,length,&right,LeftNullNode,RightNullNode);
if(!a && !b)
{
*NullNodeValue = root->data;
if(!(*length))
*length =1;
return 1;
}
if(a+b+1>*length)
{
*length = a+b+1;
*LeftNullNode = left;
*RightNullNode = right;
}
if(a>b)
{
*NullNodeValue = left;
return a+1;
}
else
{
*NullNodeValue = right;
return b+1;
}
}int FindDia(NODE root,int *length , int *NullNodeValue , int *LeftNullNode , int *RightNullNode)
{
int a,b;
int left, right;
if(root == NULL)
return 0;
a=FindDia(root->left,length,&left,LeftNullNode,RightNullNode);
b=FindDia(root->right,length,&right,LeftNullNode,RightNullNode);
if(!a && !b)
{
*NullNodeValue = root->data;
if(!(*length))
*length =1;
return 1;
}
if(a+b+1>*length)
{
*length = a+b+1;
*LeftNullNode = left;
*RightNullNode = right;
}
if(a>b)
{
*NullNodeValue = left;
return a+1;
}
else
{
*NullNodeValue = right;
return b+1;
}
}
1) Brute force method: O(n^2), n is the number of nodes. Take each node as root, we find the longest path starting from it. This can be done in O(n) using DFS or BFS. Then, we take the longest one of the longest paths starting from each node.
2) Dynamic Programming: O(n). The idea metioned above is extended to a general tree and more details are added. Take a random node as root (e.g., 0), the longest path either passes this node or not. If the longest path does not pass this node, then it must come from one of the subtrees (can be more than two). Otherwise, the longest path must pass the root and it can be divided into three parts, i.e., path from a subtree root to leave, root, path from another subtree root to leave. Then, the length of the longest path should be length(longestSubrootToLeavePath) + 1 + length(secondLongestSubrootToLeavePath) + 1.
The root to leave path is deinfed as the path from the root to the leave. Subroot is the root of one of the subtrees. Therefore, we need to find three paths for a tree, the longest path, longest path from a subtree root to a leave, the second longest path from a sub tree root a leave. Two subtrees are different otherwise the composed path from them will not pass the root. Then longest path with a tree = max(longest path passing root, longest paths in subtrees).
3) Take a random node (a), find the farthest node (b) from the node a. Then find the farthest node (c) from the node (b). Why is it correct? Think node a as the root, the node b must a leave (otherwise the leaves in this subtree must have longer paths). If the longest path passes the root, then the longest path can be divided into three parts, path1 + root + path2. It is easy to understand that path1 or path2 has to starts with node b and path2 should be the second longest path from the root. At the same time, the other end of the second longest path is the farthest node from node b. If the longest path does not pass the root, then the longest path in the subtree T (root d) of a where node b resdies. More importantly, b is still the farthest node from the root d. Therefore, we can proceed the same reasoning as above.
import java.util.*;
public class LongestPathInTree {
//tree is represented as an adjacent list
private List<List<Integer>> adjacentList;
private Path longestPath;
//is a node visited?
private boolean[] visited;
public LongestPathInTree (List<List<Integer>> adjacentList) {
this.adjacentList = adjacentList;
this.visited = new boolean[adjacentList.size()];
}
public Path getLongestPath() {
return longestPath;
}
//traverse paths between every pair of nodes
public void bruteForceMethod() {
//default path is shortest
longestPath = new Path();
for(int i = 0; i < adjacentList.size(); i++) {
Arrays.fill(visited,false);
depthFirstSearchPath(i,i,0);
}
}
public void depthFirstSearchPath(int treeRoot, int subTreeRoot, int pathLength) {
List<Integer> neighbors = adjacentList.get(subTreeRoot);
//in fact, only leave nodes need checking but it does not harm to check every node
if(pathLength > longestPath.getLength()) {
longestPath.setStart(treeRoot);
longestPath.setEnd(subTreeRoot);
longestPath.setLength(pathLength);
}
for(int u : neighbors) {
if(!visited[u]) {
visited[u] = true;
depthFirstSearchPath(treeRoot, u, pathLength+1);
}
}
}
public void dpMethod() {
Arrays.fill(visited,false);
int root = 0;
longestPath = findLongestPath(root)[0];
}
public Path[] findLongestPath(int root) {
visited[root] = true;
//longest path in subtrees (don't have to pass the subtree root)
Path longestPathInSubtrees = null;
//longest path from a subtree root to a leave
Path longestPathFromSubrootToSubLeave = null;
//second longest path from another subtree root to a leave
//the two subtrees must be different
Path secondLongestPathFromSubrootToSubLeave = null;
List<Integer> neighbors = adjacentList.get(root);
//System.out.println(neighbors);
for(int u : neighbors) {
if(!visited[u]) {
//path[0] is the longest path in a subtree
//path[1] is the longest path from the subtree root to a leave
Path[] paths = findLongestPath(u);
if(longestPathInSubtrees == null
|| longestPathInSubtrees.getLength() < paths[0].getLength()) {
longestPathInSubtrees = paths[0];
}
if(longestPathFromSubrootToSubLeave == null
|| longestPathFromSubrootToSubLeave.getLength() < paths[1].getLength()) {
secondLongestPathFromSubrootToSubLeave = longestPathFromSubrootToSubLeave;
longestPathFromSubrootToSubLeave = paths[1];
} else if(secondLongestPathFromSubrootToSubLeave == null
|| secondLongestPathFromSubrootToSubLeave.getLength() < paths[0].getLength() ) {
secondLongestPathFromSubrootToSubLeave = paths[1];
}
}
}
//System.out.println("Root: " + root);
//System.out.println(longestPathFromSubrootToSubLeave);
//System.out.println(secondLongestPathFromSubrootToSubLeave);
if (longestPathFromSubrootToSubLeave == null
&& secondLongestPathFromSubrootToSubLeave == null) {
//leave
return new Path[] {new Path(root, root, 0), new Path(root, root, 0)};
}else {
//the potential longest path passing root
Path path = null;
//this node might only have one subtree
if(secondLongestPathFromSubrootToSubLeave == null) {
path = new Path(longestPathFromSubrootToSubLeave.getEnd(),
root, longestPathFromSubrootToSubLeave.getLength()+1);
}else {
path = new Path(longestPathFromSubrootToSubLeave.getEnd(),
secondLongestPathFromSubrootToSubLeave.getEnd(),
longestPathFromSubrootToSubLeave.getLength() + 1
+ secondLongestPathFromSubrootToSubLeave.getLength() + 1);
}
Path[] paths = new Path[2];
//the longest path in the tree
paths[0] = path.getLength() > longestPathInSubtrees.getLength() ? path : longestPathInSubtrees;
//the longest path from the root to a leave
paths[1] = new Path(root, longestPathFromSubrootToSubLeave.getEnd(),
longestPathFromSubrootToSubLeave.getLength()+1);
return paths;
}
}
public void farthestFarthestMethod() {
longestPath = new Path();
int root = 0;
Arrays.fill(visited,false);
depthFirstSearchPath(root, root, 0);
Arrays.fill(visited,false);
root = longestPath.getEnd();
Arrays.fill(visited, false);
depthFirstSearchPath(root, root, 0);
}
public static void main(String[] args) {
List<List<Integer>> tree = new ArrayList<List<Integer>>();
List<Integer> ls0 = new ArrayList<Integer>();
ls0.add(1);
ls0.add(2);
ls0.add(3);
tree.add(ls0);
List<Integer> ls1 = new ArrayList<Integer>();
ls1.add(0);
ls1.add(4);
ls1.add(5);
tree.add(ls1);
List<Integer> ls2 = new ArrayList<Integer>();
ls2.add(0);
ls2.add(6);
tree.add(ls2);
List<Integer> ls3 = new ArrayList<Integer>();
ls3.add(0);
tree.add(ls3);
List<Integer> ls4 = new ArrayList<Integer>();
ls4.add(1);
ls4.add(7);
tree.add(ls4);
List<Integer> ls5 = new ArrayList<Integer>();
ls5.add(1);
ls5.add(9);
tree.add(ls5);
List<Integer> ls6 = new ArrayList<Integer>();
ls6.add(2);
ls6.add(8);
tree.add(ls6);
List<Integer> ls7 = new ArrayList<Integer>();
ls7.add(4);
ls7.add(13);
tree.add(ls7);
List<Integer> ls8 = new ArrayList<Integer>();
ls8.add(6);
tree.add(ls8);
List<Integer> ls9 = new ArrayList<Integer>();
ls9.add(5);
ls9.add(10);
tree.add(ls9);
List<Integer> ls10 = new ArrayList<Integer>();
ls10.add(9);
ls10.add(11);
tree.add(ls10);
List<Integer> ls11 = new ArrayList<Integer>();
ls11.add(10);
ls11.add(12);
tree.add(ls11);
List<Integer> ls12 = new ArrayList<Integer>();
ls12.add(11);
tree.add(ls12);
List<Integer> ls13 = new ArrayList<Integer>();
ls13.add(7);
ls13.add(14);
tree.add(ls13);
List<Integer> ls14 = new ArrayList<Integer>();
ls14.add(13);
ls14.add(15);
tree.add(ls14);
List<Integer> ls15 = new ArrayList<Integer>();
ls15.add(14);
tree.add(ls15);
LongestPathInTree finder = new LongestPathInTree(tree);
finder.bruteForceMethod();
System.out.println(finder.getLongestPath());
finder.dpMethod();
System.out.println(finder.getLongestPath());
finder.farthestFarthestMethod();
System.out.println(finder.getLongestPath());
}
}
class Path {
private int start;
private int end;
private int length;
public Path() {
this.start = Integer.MIN_VALUE;
this.end = Integer.MIN_VALUE;
this.length = Integer.MIN_VALUE;
}
public Path(int start, int end, int length) {
this.start = start;
this.end = end;
this.length = length;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
public int getLength() {
return length;
}
public void setStart(int start) {
this.start = start;
}
public void setEnd(int end) {
this.end = end;
}
public void setLength(int length) {
this.length = length;
}
public String toString() {
return "start: " + start + " end: " + end + " length: " + length;
}
}
Why are you adding Adjacency List for 0 two times.
List<Integer> ls1 = new ArrayList<Integer>();
ls1.add(0);
ls1.add(4);
ls1.add(5);
tree.add(ls1);
List<Integer> ls2 = new ArrayList<Integer>();
ls2.add(0);
ls2.add(6);
tree.add(ls2);
Longest possible path in the tree = (reverse of longest path from root in left sub tree) + root+ (longest possible path from root in right sub tree)
package trees;
/**
* Program prints the longest possible path starting at the root in a tree
* followed by the longest possible path which can start at any node in the tree
* Longest path in the tree = reverse(longest path from root in the left sub
* tree) + root + (longest path from root in the right sub tree)
*
*
* @author sriniatiisc
*
*/
public class LongestPossiblePathInTree {
public static void main(String[] args) {
TreeNode root = new TreeNode(1);
root.appendLeft(2).appendLeft(4);
root.getLeft().appendRight(5).appendLeft(8);
root.appendRight(3).appendLeft(6);
root.getRight().appendRight(7);
TreeNode.print(root);
String longestPath = longestPath(root);
System.out
.println("Longest path starting at root node: " + longestPath);
String longestLPath = longestPath(root.getLeft());
String longestRPath = longestPath(root.getRight());
System.out.println("Longest path in the tree: " + reverse(longestLPath)
+ root.getData() + longestRPath);
}
private static String longestPath(TreeNode root) {
if (root == null)
return "";
String lPath = longestPath(root.getLeft());
String rPath = longestPath(root.getRight());
if (lPath.length() >= rPath.length()) {
return root.getData() + lPath;
} else {
return root.getData() + rPath;
}
}
public static String reverse(String s) {
char[] str = s.toCharArray();
int i = 0;
int j = str.length - 1;
while (i <= j) {
char t = str[i];
str[i] = str[j];
str[j] = t;
i++;
j--;
}
return new String(str);
}
}
public int maxHeight(BinaryTree b)
{
if(b==null) return 0;
return 1 + Math.max(maxHeight(b.left), maxHeight(b.right));
}
public void maxPath(BinaryTree b,int[] a,int index)
{
if(b==null)
{
for(int i = 0; i < index;i++)
System.out.println(a[i] + "");
return;
}
a[index] = b.data;
index++;
if(maxHeight(b.left) > maxHeight(b.right))
maxPath(b.left, a, index);
else maxPath(b.right, a, index);
}
Here is a pseudo code that runs with O(n) memory and O(n) time complexity. Conceptually it’s a breadth-first-traversal with queue.
I’ve used an additional queue to hold the leafs for final answer.
Algorithm -Starts
Node marker <-- create a node object // this acts as delimiter of each levels nodes
Q1 <- initialize an empty queue
Q2 <- initialize an empty queue
//For each node removed from Q1 its children are stored again in Q1. If the removed
//node doesn’t have any children then only it will be queued to Q2.
//The loop exits when Q1 is empty and at that time all nodes in Q2 are the leafs of all longest paths from root.
Q1.enqueue(root);
Q1.enqueue(marker);
While true
Node n=Q1.dequeue();
If n is marker
If Q1 is empty
Break;
Else
Q1.enqueue(n);
Q2.removeAll();
continue
End-if
End-if
boolean hasChildren=false;
if n has left child
Q1.enqueue(n->leftChild);
hasChildren=true;
end-if
if n has right child
Q1.enqueue(n->rightChild);
hasChildren=true;
end-if
if hasChildren == false
Q2.enqueue (n);
End-if
End-while
//all nodes in Q2 are the answer
Algorithm –Ends
Your solution does not work for following input :
1
2 3
4 5
7
9
Root – 1 : left child-2, right child – 3
Node-2 : left child -4, right child -5
Nod-3 : leaf node
Node-4 : leaf node
Node-5 : left child-null, right child-7
Node-7: left child-null , right child-9
Node-9 : leaf node
With your solution Q2 contains only 9 at the end. However the correct answer is 9 and 3 which has the longest path which is 6.
Yes 'maverick' you're right. This post essenially asking us to solve diameter
of a tree and I'm mistakenly solved it for longest path from root.
[Note: The diameter of a tree is the number of nodes on the longest path
between two leaves in the tree]
Anyway, here is a solution for finding leaves of a diameter - still with O(n)
complexity for both runtime and memory for a general tree (not necessarily a binary).
To explain the concept, let's take a binary tree (BT) first. For BT each node
can be assined a number which is sum of its left-height and right-hight. Thus
this number represents the longest path thru that node at which the sub-tree
is rooted.
Now if we identify a node from the original tree that has maximum value assigned
we can get the longest path in the orignal tree which is also the diamgeter of
the subtree rooted at this node. We can also get the two leafs - one from left-subtree
and one from right subtree of this node and it will give us the two leaves
with longest path.
If you've more than one nodes having max number assinged then all such node will
generate diameter of same lenth. Moreover for any node having max number assigned
may have more than one sets of diameter leaves since left subtree can have more
than one leaves at max-depth (min height) and likewise for the right subtree.
Now extending this concept to a generic tree, we can assume the number to assign
to a node is the sum of two maximum heights from all children's height. Rest of the
logic remains same.
Algorithm: [null check and edge condition checks are omitted for simplicity. A node is
assumed have a list of references to its children]
findADiameter(Node n)
// r will be node with max assigned value and
//c1 & c2 are children nodes having max heights.
Node r=null, c1=null, c2=null;
findMaxDepthAndUpdateResults(n, -1, r, c1, c2); //use pointer or so
//such that update of r, c1 and c2 by
//findMaxDepthAndUpdateResults gets reflected in parent.
Node leaf1=bfs(c1); //bfs returns one of the leaf max depth
//from sub-tree at c
Node leaf2=nfs(c2);
//leaf1 & leaf2 are the two leafs of a diameter of the original tree.
//There could exists other leafs having same diameter. With
//additional memory they can be captured as well.
end-function
int findMaxDepthAndUpdateResults(Node n, int dia, Node r, Node c1, Node c2){ //recursive function
int h1=-1; Node cc1=null;
int h2=-1; Node cc2=null;
//h1 & h2 will hold two max height of the all children's
// hight for parent node n
// h1 >=h2 will be maintained
if n->children == null
return 0;
end-if
for each child c in n->children
h=findMaxDepthAndUpdateResults(c, dia, r, c1, c2);
if(h >= h1 )
h2=h1 ; cc2=cc1;
h1=h; cc1=c;
else if (h >= h2 )
h2=h; cc2=c;
end-if
end-for
if ( number of children of n >= 2 )
if ( (h1+1 + h2+1) > dia )
dia=h1+h2+2;
c1=cc1; c2=cc2; r=n; //update the results
end-if
end-if
return max (h1, h2) + 1 ; //return height of the node n
end-function
Your concept seems correct to me.
There are few corrections ,I think , would be needed in your pseudo code :
1. You should not return 0 when a node does not have any children.
if n->children == null
return 0;
end-if
This should return 1 , as a node without children has height 1.
Instead, you could also say that -> (f n == null) return 0.
2. Formulae of “dia” seems incorrect . It should be h1+h2+1 i.e. height of left child + height right child + parent which is 1. Am I missing something here ?
One improvement I could see: instead of preserving c1 and c2, you can preserve leaf nodes , and thus avoiding to find leaf nodes at the end i.e. essentially avoiding these two calls.
Node leaf1=bfs(c1); //bfs returns one of the leaf max depth
//from sub-tree at c
Node leaf2=nfs(c2);
maverick:
1) How a height is defined can vary as long as you're not screwing up things. It's just a conceptual convention you would like to follow just like we follow different indexing for arrays. While one can consider height of leaf node=0 other can consider 1. In my case I've considered height of leaf node as 0.
2)With this definition of height I think the diameter definition/calculation is correct as well.
Example i: A tree with root A and A has two children B & C
h1=findMaxDepthAndUpdateResults(B,...) will return 0
similarly
h2=findMaxDepthAndUpdateResults(B,...) will return 0
Now diameter from B to C is 2 since number of edges in B-A-C is 2.
In my calculation dia=h1+1+h2+1=2 seems correct as per my definition of height.
Example ii: Root A has two children B & C and C has another right child D. Applying the algorithm you'll get
h1=findMaxDepthAndUpdateResults(B,...) will return 0
similarly
h2=findMaxDepthAndUpdateResults(C,...) will return 1
thus dia of A= h1+1+h2+1=0+1+1+1=3 which is in accordance with edges in the path B-A-C-D
Example iii: Root A has one child B
In such cases I've omitted calculating diameter since there doesn't exists at least two leaves to form a diameter
3) Finally, would you please. write some pseudo/concrete code on how the leaf can be stored avoiding the two BFS'!!
While I understand what you are saying , but :
1. I am normally cautious and picky about the word “height” used with Tree. Wikipedia says height of tree with only one node which is root is 0. But I have experienced that people normally perceive it as an empty tree. So I am always careful , while mentioning the height of the tree.
The problem is not with the height definition you have chosen , but the actual problem is it reduces the diameter of tree. ( see below )
2. Check out the diameter of the tree definition (geeksforgeeks.org/archives/5687). It’s not the number of edges but its number of nodes.
Since your focus is to just find two leave nodes which forms the diameter of the tree , it really does not matter what definition you assume for heights and diameter. Your solution is still correct in terms finding two leaf nodes which forms diameter.
But your diameter value seems incorrect to me.
3. It is just an idea and its very much possible. Try modifying your code , you can get the answer and if you cannot please do tell me.
maverick:
The concept of Diameter of a tree is derived from the concept of diameter of a graph in Graph Theory. The mathematical definition is
diameter of G = max ( d(u,v) ) for all u,v in G
G is a graph
u,v are any vertices in G
d(u,v) is the distance between u and v and is calculated based on number of edges in a path with two ends as u and v
Thus Graph theoretically diameter is calculated based on number of edges and not on the nodes.
Bibliography:
[1] mathworld.wolfram.com/GraphDiameter.html
[2] mathworld.wolfram.com/GraphDistance.html
[3] mathworld.wolfram.com/GraphEccentricity.html
[4] A Beginner's Guide to Graph Theory By W.D. Wallis. Chapter 2.2 Radius, Diameter and Eccentricity [worked out example. Available in google books.
books.google.co.in/books?id=mpPK7CWEQnkC&pg=PA23&source=gbs_toc_r&cad=4#v=onepage&q&f=false ]
Regarding your proposal for storing leafs and avoiding BFS's, it's official that I cannot solve it. Please show us how to do it!!
I started to believe that its number of edges not number of nodes. All the sites or discussions forum saying that its number nodes , seems wrong. Thanks for enlightening us.
For preserving leaf nodes , answer lies in this discussion thread itself . Check out the code posted by “Lucy on October 12, 2012” . It looks correct to me, provide your comment there if you see anything wrong in Lucy’s code so that she would get to know.
This code is very similar to what you are doing , except it preserves leaf nodes which forms diameter.
In case if you get confused looking at Lucy’s code, length is actually the diameter and findDia() function returns height of the root.
ArrayList<Node> leafNodes = new ArrayList<Node>(); // Required Anser
CALL : longPathNodes(root, leafNodes);
Function:
int longPathNodes(Node node, ArrayList<Node> leafNodes){
if(node == null){
return 0;
}
ArrayList<Node> leftNodes = new ArrayList<Node>(0);
ArrayList<Node> rightNodes = new ArrayList<Node>(0);
int leftSize = longPathNodes(node.left, leftNodes);
int rightSize = longPathNodes(node.right, rightNodes);
if(leftSize == rightSize){
leafNodes.addAll(leftNodes);
leafNodes.addAll(rightNodes);
}else if (leftSize > rightSize){
leafNodes.addAll(leftNodes);
} else {
leafNodes.addAll(rightNodes);
}
if(leftSize == 0 && rightSize==0){
leafNodes.add(node);
}
return Math.max(leftSize, rightSize)+1;
}
def findFarthestLeaf(root,level,maxLevel,leaf):
if root==None:
return 0,None
if root.left==None and root.right==None:
if level>=maxLevel:
return level,root
else:
return maxLevel,leaf
if root.left!=None:
level,leaf = findFarthestLeaf(root.left,level+1,maxLevel,leaf)
if root.right!=None:
level,leaf = findFarthestLeaf(root.right,level+1,maxLevel,leaf)
return level,leaf
findFarthestLeaf(root,0,0,None)
- Albertone9 October 31, 2012