Skip to content

Commit 2210291

Browse files
committed
tree interview sol
1 parent a2afef3 commit 2210291

15 files changed

+1191
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Binary Search Tree Check - SOLUTION\n",
8+
"\n",
9+
"## Problem Statement\n",
10+
"\n",
11+
"Given a binary tree, check whether it’s a binary search tree or not.\n",
12+
"\n",
13+
"** Again, no solution cell, just worry about your code making sense logically. Hint: Think about tree traversals. **\n",
14+
"\n",
15+
"## Solution\n",
16+
"\n",
17+
"Here is a simple solution- If a tree is a binary search tree, then traversing the tree inorder should lead to sorted order of the values in the tree. So, we can perform an inorder traversal and check whether the node values are sorted or not. "
18+
]
19+
},
20+
{
21+
"cell_type": "code",
22+
"execution_count": 7,
23+
"metadata": {
24+
"collapsed": false
25+
},
26+
"outputs": [
27+
{
28+
"name": "stdout",
29+
"output_type": "stream",
30+
"text": [
31+
"True\n"
32+
]
33+
}
34+
],
35+
"source": [
36+
"tree_vals = []\n",
37+
"\n",
38+
"def inorder(tree):\n",
39+
" if tree != None:\n",
40+
" inorder(tree.getLeftChild())\n",
41+
" tree_vals.append(tree.getRootVal())\n",
42+
" inorder(tree.getRightChild())\n",
43+
" \n",
44+
"def sort_check(tree_vals):\n",
45+
" return tree_vals == sorted(tree_vals)\n",
46+
"\n",
47+
"inorder(tree)\n",
48+
"sort_check(tree_vals)"
49+
]
50+
},
51+
{
52+
"cell_type": "markdown",
53+
"metadata": {},
54+
"source": [
55+
"Another classic solution is to keep track of the minimum and maximum values a node can take. And at each node we will check whether its value is between the min and max values it’s allowed to take. The root can take any value between negative infinity and positive infinity. At any node, its left child should be smaller than or equal than its own value, and similarly the right child should be larger than or equal to. So during recursion, we send the current value as the new max to our left child and send the min as it is without changing. And to the right child, we send the current value as the new min and send the max without changing."
56+
]
57+
},
58+
{
59+
"cell_type": "code",
60+
"execution_count": 8,
61+
"metadata": {
62+
"collapsed": false
63+
},
64+
"outputs": [
65+
{
66+
"name": "stdout",
67+
"output_type": "stream",
68+
"text": [
69+
"True\n",
70+
"False\n"
71+
]
72+
}
73+
],
74+
"source": [
75+
"class Node:\n",
76+
" def __init__(self, k, val):\n",
77+
" self.key = k\n",
78+
" self.value = val\n",
79+
" self.left = None\n",
80+
" self.right = None\n",
81+
"\n",
82+
"def tree_max(node):\n",
83+
" if not node:\n",
84+
" return float(\"-inf\")\n",
85+
" maxleft = tree_max(node.left)\n",
86+
" maxright = tree_max(node.right)\n",
87+
" return max(node.key, maxleft, maxright)\n",
88+
"\n",
89+
"def tree_min(node):\n",
90+
" if not node:\n",
91+
" return float(\"inf\")\n",
92+
" minleft = tree_min(node.left)\n",
93+
" minright = tree_min(node.right)\n",
94+
" return min(node.key, minleft, minright)\n",
95+
"\n",
96+
"def verify(node):\n",
97+
" if not node:\n",
98+
" return True\n",
99+
" if (tree_max(node.left) <= node.key <= tree_min(node.right) and\n",
100+
" verify(node.left) and verify(node.right)):\n",
101+
" return True\n",
102+
" else:\n",
103+
" return False\n",
104+
"\n",
105+
"root= Node(10, \"Hello\")\n",
106+
"root.left = Node(5, \"Five\")\n",
107+
"root.right= Node(30, \"Thirty\")\n",
108+
"\n",
109+
"print(verify(root)) # prints True, since this tree is valid\n",
110+
"\n",
111+
"root = Node(10, \"Ten\")\n",
112+
"root.right = Node(20, \"Twenty\")\n",
113+
"root.left = Node(5, \"Five\")\n",
114+
"root.left.right = Node(15, \"Fifteen\")\n",
115+
"\n",
116+
"print(verify(root)) # prints False, since 15 is to the left of 10"
117+
]
118+
},
119+
{
120+
"cell_type": "markdown",
121+
"metadata": {},
122+
"source": [
123+
"This is a classic interview problem, so feel free to just Google search \"Validate BST\" for more information on this problem!"
124+
]
125+
},
126+
{
127+
"cell_type": "markdown",
128+
"metadata": {},
129+
"source": [
130+
"## Good Job!"
131+
]
132+
}
133+
],
134+
"metadata": {
135+
"kernelspec": {
136+
"display_name": "Python 2",
137+
"language": "python",
138+
"name": "python2"
139+
},
140+
"language_info": {
141+
"codemirror_mode": {
142+
"name": "ipython",
143+
"version": 2
144+
},
145+
"file_extension": ".py",
146+
"mimetype": "text/x-python",
147+
"name": "python",
148+
"nbconvert_exporter": "python",
149+
"pygments_lexer": "ipython2",
150+
"version": "2.7.11"
151+
}
152+
},
153+
"nbformat": 4,
154+
"nbformat_minor": 0
155+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Tree Level Order Print - SOLUTION\n",
8+
"\n",
9+
"Given a binary tree of integers, print it in level order. The output will contain space between the numbers in the same level, and new line between different levels. For example, if the tree is: \n",
10+
"___\n",
11+
"![title](tree_print.png)\n",
12+
"___\n",
13+
"The output should be: \n",
14+
"\n",
15+
" 1 \n",
16+
" 2 3 \n",
17+
" 4 5 6"
18+
]
19+
},
20+
{
21+
"cell_type": "markdown",
22+
"metadata": {},
23+
"source": [
24+
"## Solution\n",
25+
"\n",
26+
"It won’t be practical to solve this problem using recursion, because recursion is similar to depth first search, but what we need here is breadth first search. So we will use a queue as we did previously in breadth first search. First, we’ll push the root node into the queue. Then we start a while loop with the condition queue not being empty. Then, at each iteration we pop a node from the beginning of the queue and push its children to the end of the queue. Once we pop a node we print its value and space.\n",
27+
"\n",
28+
"To print the new line in correct place we should count the number of nodes at each level. We will have 2 counts, namely current level count and next level count. Current level count indicates how many nodes should be printed at this level before printing a new line. We decrement it every time we pop an element from the queue and print it. Once the current level count reaches zero we print a new line. Next level count contains the number of nodes in the next level, which will become the current level count after printing a new line. We count the number of nodes in the next level by counting the number of children of the nodes in the current level. Understanding the code is easier than its explanation:"
29+
]
30+
},
31+
{
32+
"cell_type": "code",
33+
"execution_count": 1,
34+
"metadata": {
35+
"collapsed": true
36+
},
37+
"outputs": [],
38+
"source": [
39+
"class Node:\n",
40+
" def __init__(self, val=None):\n",
41+
" self.left = None\n",
42+
" self.right = None\n",
43+
" self.val = val "
44+
]
45+
},
46+
{
47+
"cell_type": "code",
48+
"execution_count": 2,
49+
"metadata": {
50+
"collapsed": true
51+
},
52+
"outputs": [],
53+
"source": [
54+
"def levelOrderPrint(tree):\n",
55+
" if not tree:\n",
56+
" return\n",
57+
" nodes=collections.deque([tree])\n",
58+
" currentCount, nextCount = 1, 0\n",
59+
" while len(nodes)!=0:\n",
60+
" currentNode=nodes.popleft()\n",
61+
" currentCount-=1\n",
62+
" print currentNode.val,\n",
63+
" if currentNode.left:\n",
64+
" nodes.append(currentNode.left)\n",
65+
" nextCount+=1\n",
66+
" if currentNode.right:\n",
67+
" nodes.append(currentNode.right)\n",
68+
" nextCount+=1\n",
69+
" if currentCount==0:\n",
70+
" #finished printing current level\n",
71+
" print '\\n',\n",
72+
" currentCount, nextCount = nextCount, currentCount"
73+
]
74+
},
75+
{
76+
"cell_type": "markdown",
77+
"metadata": {},
78+
"source": [
79+
"The time complexity of this solution is O(N), which is the number of nodes in the tree, so it’s optimal. Because we should visit each node at least once. The space complexity depends on maximum size of the queue at any point, which is the most number of nodes at one level. The worst case occurs when the tree is a complete binary tree, which means each level is completely filled with maximum number of nodes possible. In this case, the most number of nodes appear at the last level, which is (N+1)/2 where N is the total number of nodes. So the space complexity is also O(N). Which is also optimal while using a queue. \n",
80+
"\n",
81+
"Again, this is a very common tree interview question!"
82+
]
83+
},
84+
{
85+
"cell_type": "markdown",
86+
"metadata": {},
87+
"source": [
88+
"## Good Job!"
89+
]
90+
}
91+
],
92+
"metadata": {
93+
"kernelspec": {
94+
"display_name": "Python 2",
95+
"language": "python",
96+
"name": "python2"
97+
},
98+
"language_info": {
99+
"codemirror_mode": {
100+
"name": "ipython",
101+
"version": 2
102+
},
103+
"file_extension": ".py",
104+
"mimetype": "text/x-python",
105+
"name": "python",
106+
"nbconvert_exporter": "python",
107+
"pygments_lexer": "ipython2",
108+
"version": "2.7.11"
109+
}
110+
},
111+
"nbformat": 4,
112+
"nbformat_minor": 0
113+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Trim a Binary Search Tree - SOLUTION\n",
8+
"\n",
9+
"## Problem Statement\n",
10+
"\n",
11+
"Given the root of a binary search tree and 2 numbers min and max, trim the tree such that all the numbers in the new tree are between min and max (inclusive). The resulting tree should still be a valid binary search tree. So, if we get this tree as input:\n",
12+
"___\n",
13+
"\n",
14+
"![title](bst1.png)\n",
15+
"___\n",
16+
"and we’re given **min value as 5** and **max value as 13**, then the resulting binary search tree should be: \n",
17+
"___\n",
18+
"![title](bst_trim.png)\n",
19+
"___\n",
20+
"We should remove all the nodes whose value is not between min and max. \n",
21+
"\n",
22+
"___\n",
23+
"** Feel free to reference the lecture on Binary Search Tree for the node class, but what it more important here is the logic of your function. In which case your function should be in the form:**"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": 1,
29+
"metadata": {
30+
"collapsed": true
31+
},
32+
"outputs": [],
33+
"source": [
34+
"def trimBST(tree,minVal,maxVal):\n",
35+
" \n",
36+
" print tree.left # LeftChild\n",
37+
" print tree.right # Right Child\n",
38+
" print tree.val # Node's value\n",
39+
" \n",
40+
" pass\n",
41+
"\n",
42+
"# Use tree.left , tree.right , and tree.val as your methods to call"
43+
]
44+
},
45+
{
46+
"cell_type": "markdown",
47+
"metadata": {},
48+
"source": [
49+
"** There is no solution cell because the nature of the code should be more logical and a test would essentially give away the answer. Just focus your answer to fill out the logic of the solution using the methods described above **\n",
50+
"\n",
51+
"## Best of luck!"
52+
]
53+
},
54+
{
55+
"cell_type": "markdown",
56+
"metadata": {},
57+
"source": [
58+
"## Solution\n",
59+
"\n",
60+
"We can do this by performing a post-order traversal of the tree. We first process the left children, then right children, and finally the node itself. So we form the new tree bottom up, starting from the leaves towards the root. As a result while processing the node itself, both its left and right subtrees are valid trimmed binary search trees (may be NULL as well).\n",
61+
"\n",
62+
"At each node we’ll return a reference based on its value, which will then be assigned to its parent’s left or right child pointer, depending on whether the current node is left or right child of the parent. If current node’s value is between min and max (min<=node<=max) then there’s no action need to be taken, so we return the reference to the node itself. If current node’s value is less than min, then we return the reference to its right subtree, and discard the left subtree. Because if a node’s value is less than min, then its left children are definitely less than min since this is a binary search tree. But its right children may or may not be less than min we can’t be sure, so we return the reference to it. Since we’re performing bottom-up post-order traversal, its right subtree is already a trimmed valid binary search tree (possibly NULL), and left subtree is definitely NULL because those nodes were surely less than min and they were eliminated during the post-order traversal. Remember that in post-order traversal we first process all the children of a node, and then finally the node itself.\n",
63+
"\n",
64+
"Similar situation occurs when node’s value is greater than max, we now return the reference to its left subtree. Because if a node’s value is greater than max, then its right children are definitely greater than max. But its left children may or may not be greater than max. So we discard the right subtree and return the reference to the already valid left subtree. The code is easier to understand:"
65+
]
66+
},
67+
{
68+
"cell_type": "code",
69+
"execution_count": 4,
70+
"metadata": {
71+
"collapsed": true
72+
},
73+
"outputs": [],
74+
"source": [
75+
"def trimBST(tree, minVal, maxVal): \n",
76+
" \n",
77+
" if not tree: \n",
78+
" return \n",
79+
" \n",
80+
" tree.left=trimBST(tree.left, minVal, maxVal) \n",
81+
" tree.right=trimBST(tree.right, minVal, maxVal) \n",
82+
" \n",
83+
" if minVal<=tree.val<=maxVal: \n",
84+
" return tree \n",
85+
" \n",
86+
" if tree.val<minVal: \n",
87+
" return tree.right \n",
88+
" \n",
89+
" if tree.val>maxVal: \n",
90+
" return tree.left "
91+
]
92+
},
93+
{
94+
"cell_type": "markdown",
95+
"metadata": {},
96+
"source": [
97+
"The complexity of this algorithm is O(N), where N is the number of nodes in the tree. Because we basically perform a post-order traversal of the tree, visiting each and every node one. This is optimal because we should visit every node at least once. This is a very elegant question that demonstrates the effectiveness of recursion in trees. "
98+
]
99+
},
100+
{
101+
"cell_type": "markdown",
102+
"metadata": {},
103+
"source": [
104+
"# Good Job!"
105+
]
106+
}
107+
],
108+
"metadata": {
109+
"kernelspec": {
110+
"display_name": "Python 2",
111+
"language": "python",
112+
"name": "python2"
113+
},
114+
"language_info": {
115+
"codemirror_mode": {
116+
"name": "ipython",
117+
"version": 2
118+
},
119+
"file_extension": ".py",
120+
"mimetype": "text/x-python",
121+
"name": "python",
122+
"nbconvert_exporter": "python",
123+
"pygments_lexer": "ipython2",
124+
"version": "2.7.11"
125+
}
126+
},
127+
"nbformat": 4,
128+
"nbformat_minor": 0
129+
}

0 commit comments

Comments
 (0)