Amazon Interview Question
Software Engineer / DevelopersCountry: United States
Interview Type: In-Person
@moiskim, in your solution - you are assuming that the array 'g' contains all the elements of the tree. That's not true and moreover, all the elements in the tree wouldn't fall in the same path.
you need to find min and max of the group g and then select left path or right path depending on condition -
if(node.data <= max )
check_left;
else
check_right;
Failure case for above code -
tree : g: { 10, 5, 7 }
10
5 40
1 7 30
1. Sort the array => g_sorted.
Repeat the following until (!g_sorted.empty() || current_node != NULL)
{
2. if max(g_sorted) > current_node.val && min(g_sorted) < current_node.val => return false;
3. if max(g_sorted) < current_node.val => go left;
3. if min(g_sorted) > current_node.val => go right;
4. if max(g_sorted) == current_node.val => remove max(g_sorted) from the array, go to Step 2.
5. if min(g_sorted) == current_node.val => remove min(g_sorted) from the array, go to Step 2.
}
if(!g_sorted.empty()) return false
Complexity is O(log(n))
1.Hash nodes of the bst in preorder, based on their ordering and not their values. This help prevent collision and also save us some space (e.g., if the range of the elements of nodes is very large)
void HashValuesPreOrder(struct bst *b, int hash[], int &index)
{
if(b)
{
hash[index++]=b->data; //hashed by their order and not their vallues
HashValuesPreOrder(b->lc,hash,index);
HashValuesPreOrder(b->rc,hash,index);
}
}
2.find the first element of the given array in the hash table:
int FindElement(int val,int hash[], int n)
{
int h=val%n;
if(hash[h]==val)
return h;
int pos=(h+1)%n;
while(pos!=h-1)
{
if(hash[pos]==val)
return pos;
pos=(pos+1)%n;
}
return -1;
}
3. Check to see if the order of the elements of the array matches the order of the element of the hash, given the starting index of the first element of the array in the hash table:
int SamePathArrayElements(int a[], int hash[], int n)
{
int i;
int prev=-1;
int pos=FindElement(a[0],hash,n+1);
if(pos==-1)
return 0;
for(i=pos;i<sizeof(a)/sizeof(a[0]);i++)
{
if(hash[i]==a[i] && i==prev-1)
prev=i;
else
return 0;
}
return 1;
}
First 3 steps approach above is O(Number_of_bst_Nodes) time as well as space.
In case the elements of the array to be searched are not expected to fit a path in their current order in this array, then it is necessary to hash the nodes in their in-order traversal (i.e., hash[b->data]++; hence duplicates are not lost), followed by sorting of the array. Then we can check the condition by:
for(i=0;i<ArrayLen;i++)
{
if(hash[a[i]])
hash[a[i]]--;
else
return 0;
}
return 1;
However, this scenario requires O(Largest_Element_of_BST) space. Time complexity is still O(Number_of_bst_Nodes) due to hashing of these values.
I am sorry for this, but step three function should be slightly modified:
int SamePathArrayElements(int a[], int hash[], int n, int aSize)
{
int i;
int j=1;
int prev=-1;
int pos=FindElement(a[0],hash,n+1);
if(pos==-1)
return 0;
for(i=pos+1;i<pos+aSize;i++)
{
printf("\n%d\t %d\n",a[j],hash[i]);
if(hash[i]==a[j] && (j==prev+1 || prev==-1))
{
prev=j;
j++;
}
else
return 0;
}
return 1;
}
If we are searching the array elements in the same order that they are appeared in their corresponding array, then we can check if they lie on the same path of the bst in O(lg n).
int IsOrder_the_Same(struct bst *b, int a[], int aSize)
{
if(b==NULL)
return 0;
int n=sizeof(a)/sizeof(a[0]);
int indx=1;
while(b!=NULL)
{
if(b->data==a[0])
{
struct bst *tmp=b;
while(tmp!=NULL)
{
if(indx==aSize)
return 1;
if(b->data>a[indx])
{
if(b->lc!=NULL && b->lc->data==a[indx])
{
indx++;
b=b->lc;
}
else
return 0;
}
else
{
if(b->rc!=NULL && b->rc->data==a[indx])
{
indx++;
b=b->rc;
}
else
return 0;
}
}
}
else if(b->data>a[0])
b=b->lc;
else
b=b->rc;
}
return 0;
}
What do you mean by same path?
Inorder, preorder, postorder, path to leaf node, which path exactly?
Basic approach
1) Convert g (which I'll assume is an int[]) into an object of a new class, which I'll call ShrinkableOrderedList, which defines methods:
size()
leftVal() [min]
rightVal() [max]
popLeft() [remove min val]
popRight() [remove max val]
2) Write a simple tree descent algorithm that pops off one end or the other as it descends.
Call g.length "m".
Step 1 efficiency is O(m*log(m)).
Step 2 efficiency is max(O(m), O(log(n)), where log(n) is the depth of the tree.
One assumes that log(n) is much greater than m, so the overall performance is probably dominated by the size of the tree and is O(log(n)).
Code:
public boolean allOnSamePath(int[] g, Node tree) {
ShrinkableOrderedList list = ShrinkableOrderedList.fromNumbers(g);
return allOnSamePath(list, tree);
}
public boolean allOnSamePath(ShrinkableOrderedList list, Node node) {
if (list.size() == 0)
return true;
if (node == null)
return false;
int nodeVal = node.key;
if (list.rightVal() < nodeVal)
return allOnSamePath(list, node.left);
else if (list.rightVal() == nodeVal) {
list.popRight();
return allOnSamePath(list, node.left);
} else if (list.leftVal() > nodeVal)
return allOnSamePath(list, node.right);
else if (list.leftVal() == nodeVal) {
list.popLeft();
return allOnSamePath(list, node.right);
} else
return false;
}
In case you're interested, here is an implementation of ShrinkableOrderedList:
class ShrinkableOrderedList
{
private int[] values;
private int leftI = 0;
private int rightI = -1;
public static ShrinkableOrderedList forValues(int[] ints) {
ShrinkableOrderedList inst = new ShrinkableOrderedList(ints.length);
Set<Integer> ordered = new TreeSet<Integer>();
for (int i : ints)
ordered.add(i);
for (Integer i: ordered)
inst.values[++inst.rightI] = i;
inst.rightI--;
}
private ShrinkableOrderedList() {
//private default constructor
}
private ShrinkableOrderedList(int len) {
values = new int[len];
}
public int size() {
return rightI - leftI + 1;
}
public int leftVal() {
return values[leftI];
}
public int rightVal() {
return values[rightI];
}
public void popLeft() {
leftI++;
}
public void popRight() {
rightI--;
}
}
This was my solution, but I forgot to give a name.
Please remove [in your mind] the last step of the static initializer. [Left over from previous version of code.]
Let's talk in pseudocode:
Let |g| = m, |Tree| = n
1) Order "g" making it easier to search in it O(m*lg(m))
2) Have a "seen in g" boolean array of same size as g.
3) Do a DFS on the tree (preorder traversal):
If not a null pointer/reference:
a) When you are in a node, check if the current node's data
is in g via binary search.
b) If it is in g, then check if an ancestor has not already seen this element of g (that is, if "seen" is not already true). If not, this is the first node in current path could could have seen this, so set the corresponding flag to true.
c) Visit left child, visit right child
d) Unset the "seen" flag IF we are the ones who set it in b).
If DFS is passed a null pointer or reference:
Check the "seen" flags to see if they are all true... if so we have a match.
bool isOnSamePath(int nums[], Root* root)
{
sortasc(nums);
int smallest = smallest(nums);
int greatest = greatest(nums);
int val = root->getVal();
int len = (lengthOf(nums)/2)-1;
if(val > smallest && val < greatest && !existInList(val, nums))
{
return false; // The root breaks the path
}
if(val > smallest & val < greatest && existInList(val, nums))
{
int left[len], right[len];
getLeftElemsOfval(left, nums, val);
getRightElemsOfVal(right, nums, val);
return isOnSamePath(isOnSamePath(left, root->getLeft())&& isOnSamePath(right, root->getRight());
}
if(val == greatest && root->getLeft() != null)
{
int left[len-1];
getLeftElemsOfVal(left, nums, val);
return isOnSamePath(left, root->getLeft());
}
else
{
return true;
}
if(val == smallest && root->getRight() != null)
{
int right[len-1];
getRightElemsOfVal(right, nums, val);
return isOnSamePath(right, root->getRight());
}
else
{
return true;
}
}
SMALL MISTAKE:
int len = (lengthOf(nums)/2)-1; should be just int len(lengthOf(nums)/2)
Few Corrections here regading my comments above.
1. Looks like sorting does not hurt unless directionality is brought into question
2. There is a small mistake at the beginning of the function the length of the array should be calculated as:
int len = (lengthOf(nums)/2); //Error made in above code. It subtracts one
from it.
All comments are very welcome +ve or -ve. Please +1 if you like this solution...
1. Sort the array => g_sorted.
Repeat the following until (!g_sorted.empty() || current_node != NULL)
{
2. if max(g_sorted) > current_node.val && min(g_sorted) < current_node.val => return false;
3. if max(g_sorted) < current_node.val => go left;
3. if min(g_sorted) > current_node.val => go right;
4. if max(g_sorted) == current_node.val => remove max(g_sorted) from the array, go to Step 2.
5. if min(g_sorted) == current_node.val => remove min(g_sorted) from the array, go to Step 2.
}
if(!g_sorted.empty()) return false
Algo :
1) Use hash to check if the current node is in the array.
2) Take a boolean array which will be used to keep track of found elemets.
3) Global count variable to keep track of number of elements found.
4) Do an in-order traversal :
5) for each node check if the element is part of array in the hash if yes then mark that element as found in boolean array and increase the count.
6) While returning back from that recursion reset the value in the boolean array and decrease the count variable.
7) When count is equal to length of the array it says that we found the path.
Space : O(number of elements in the array) -> Hash and boolean hash/array
Time : O(number of nodes in the tree)
This approach was easiest to visualize for me. Assumes large tree and small targetList. If targetList is very large, may be more efficient to remove the IsLargestItem and IsSmallestItem checks.
Basically, you recurse down left and right sides of tree, removing items from targetlist as you encounter them. If list on either side is empty, that means there exists a path that contains all items in targetList.
public static bool CheckForPath(Node root, List<int> targetList)
{
if (root == null || targetList.Count == 0) return false;
// handle case where tree contains only root that is in targetList
if (targetList.Contains(root.Data))
{
targetList.Remove(root.Data);
if (targetList.Count == 0)
return true;
}
// clone lists for left and right sides of tree
var leftList = targetList.ToList();
var rightList = targetList.ToList();
CheckForPathHelper(root.Left, leftList);
CheckForPathHelper(root.Right, rightList);
// if we found a valid path on either side, that side's list will have zero elements
return leftList.Count == 0 || rightList.Count == 0;
}
public static void CheckForPathHelper(Node current, List<int> targetList)
{
if (current == null) return;
if (targetList.Contains(current.Data))
{
// current node is in G, so remove from G
targetList.Remove(current.Data);
}
if (targetList.Count > 0)
{
// recurse left if there's still smaller items in G
if (current.Left != null && !IsSmallestItem(targetList, current.Data))
CheckForPathHelper(current.Left, targetList);
// recurse right if there's still larger items in G
if (current.Right != null && !IsLargestItem(targetList, current.Data))
CheckForPathHelper(current.Right, targetList);
}
}
private static bool IsLargestItem(List<int> list, int i)
{
return list.Max(x => x) == i;
}
private static bool IsSmallestItem(List<int> list, int i)
{
return list.Min(x => x) == i;
}
Sort your g array.
Create a boolean array, say "test" of same size as g, set all to false.
Have a binary_search routing that returns the index of match in g, or -1 on failure.
check(Tree T)
{
flag_already_set = false;
if(! T )
{
//end of a path, add your check of "test" for all true here
return;
}
//at a node: binary search the data in g array, set flag if found
if( (ret = binary_search(g, T->data) ) != -1 )
{
if( g[ret] == true ) flag_already_set = true;
else g[ret] == true;
}
check(T->left);
check(T->right);
if(ret != -1 && flag_already_set == false)
g[ret]=false; // this node set the flag, so must unwind it
}
sort the vector array in increasing order
and then call
steps
1) sort the input array g
2) if root <= min then find if same path in root->right
3) if root >= max then find if same path in root->left
4) if min<root<max then not same path
bool ifsamepath(BinaryTree * root,vector<int> b,int length)
{
if(root == NULL && length!=0)
return false;
if(length==0)
return true;
if(root->data == b[0])
{
b.erase(b.begin());
return ifsamepath(root->right,b,length-1);
}
else if(root->data == b[length-1])
{
b.erase(b.end());
return ifsamepath(root->left,b,length-1);
}
else
{
if(root->data > b[length-1])
return ifsamepath(root->left,b,length);
else if(root->data < b[0])
return ifsamepath(root->right,b,length);
else
{
return false;
}
}
}
This problem may be solved without sorting.
Let's call the size of group 'g' G(n) and size of BST as N.
Step 1) Time : O( G(n) )
Find the max and minimum element in the group 'g'.
Step 2) Time : O( log(N) )
Search for min and max elements of group g (Previously calculated) in the BST. Verify if these have the same path, if they do, all the intermediate elements will also have the same path.
The correctness is not proved.
I can show a counter-example
a BST
level 1: 6
level 2: 2 x
level 3: x 4 x x
level 4: x x 3 5 x x x x
the array: [2,3,4,5,6]
Can you explain what is exactly meant by same path here ?
I have presumed that we are comparing the path from root to the point below which some group element is present along the path.
So essentially the path must not have any group element in it.
For instance :
level 1: A
level 2: B x
level 3: x G1 x x
level 4: x x G2 G3 x x x x
In this tree I would say that the group elements have a common path ( A->B ).
mingjun is correct. No guarantee that all of the given elements in the set will fall on the path between min and max. The set g can have arbitrary elements.
However, if you store the elements of the group g in a hashset(an extra O(G(n)) space), then on the path starting from min, RECURSIVELY check both the children to see if either or both of them belong to the group g. In other words, do an Inclusive-OR of the recursive calls to return the result.
A small edit:
>> on the path starting from min
should be
on the path starting from the right child of min.
@Erasmus and - @mingjun : Can you please answer what happens with the tree - mingjun has given. Does it have common ancestors or not ? If you feel the answer is NO, please give an example of some BST with the same group 'g' for which you would answer YES.
As far as I am concerned, my answer would be YES for this case, and my suggested algorithm would result with the same answer.
see you are getting maximum and minimum and you will keep removing and again doing the min and max
so do sort once it will be less complex... only one min and max wont work
@kkr.ashish : No I am not removing any element. All I have done is to trace paths( see my special path defintion in my 1st cmmnt) of min and max element once. That's it. If they match answer YES, if they dont answer NO. Simple as that :)
The correctness is not proved.
I can show a counter-example
a BST
level 1: 6
level 2: 2 x
level 3: x 4 x x
level 4: x x 3 5 x x x x
the array: [2,3,4,5,6]
as pointed out you will give yes for this but the answer is no
a path is defined from root to a single leaf node
here 6 - 2- 4 - 5 is one path
and 6-2-4-3 is another path so the array is not on single path
@kkr.ashish : Well, with your assumption you will never have same path for any array of size greater than 2. So, by same path I assume, that path from root to lowest node in the path such that no group element is present in the path.
For instance,
Group array : { G1, G2 , G3, G4 .... }
path to some element GN : A->B->C->D->G3->F->G1->GN
then I assume path is : A->B->C->D
If you think my assumption is wrong, Please give an example where the array size is more than 2 and all elements have common path.
example: [6,2,4,5] for the previous tree
BST
level 1: 6
level 2: 2 x
level 3: x 4 x x
level 4: x x 3 5 x x x x
i couldn't understand rest of your post
the above BST two path are visible because rest of element are x,
which are 6->2->4->5
and 6->2->4->3
i dont know if you are assuming group as a continguous subarray of the sorted numbers in BST which is not mentioned in question
@kkr.ashish : Just give me an example of a tree which has common path for all array elements and array size > 2.
PS : I think with your assumption of path, there can't be common paths for all array elements. That's because, for the 3rd element to have same path we will need its immediate parent to be parents of 3 nodes, which is not possible in "Binary" search tree
read about path and Trees
you will understand
the question is not that they have same path
the question is they are on same path
.. i cant explain more
thanks
My solution is
augment the BST, for each node in the Tree save 2 flags: left_child_visited right_child_visited
For each element in the array, search it in BST, and update the flags
Within the search, if we met a node with both flags as TRUE, then return : NOT in a single path.
At the end of the search, return: in a single path.
For size of BST = N, size of Array = n,
time complexity is O(n * log N)
1.Sort the elements to be searched to be stored in arr[]
2.Now Start from the root of bst.
2.1 search root element in the array arr
if it's index is index>0 or (index < arr.length -1) return false
else if index is 0 then move to Tree's right and arr is from 1 to arr.length-1
or if index is arr.length-1 move to tree's left and arr is now from 0 to arr.length-2;
at the end arr should be empty.
return true
1. sort the elements in g.
2. traverse the tree in in-order. Since in-order traverses each node from smallest to the biggest in ascending order, both g, the each node in in-order traverse should be the same.
so
int g[] = { 2,3,4,3,1,0}
quicksort( g );
and call inorder( node);
bool inorder( node* hd)
{
static int n = 0;
if ( hd->left) inorder( hd->left);
if (g[n++] != hd->data)
return false ;
if ( hd->right) inorder( hd_>right);
}
quicksort
No need to sort the array. Iterate on the array and find each element in the tree. Mark all the nodes visited during the traversal.
Use a hash to track the discovered numbers. O(g) extra space.
Time complexity : O(g+depth of tree)
- prasanth September 12, 2013