Skip to content

Commit d07458d

Browse files
authoredSep 2, 2020
Merge pull request amejiarosario#73 from amejiarosario/feat/tree-problems
Feat/tree problems

17 files changed

+554
-125
lines changed
 

‎book/D-interview-questions-solutions.asc

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ A better solution is to eliminate the 2nd for loop and only do one pass.
8383
Algorithm:
8484

8585
- Do one pass through all the prices
86-
- Keep track of the minimum price seen so far.
87-
- calculate `profit = currentPrice - minPriceSoFar`
88-
- Keep track of the maximun profit seen so far.
86+
** Keep track of the minimum price seen so far.
87+
** calculate `profit = currentPrice - minPriceSoFar`
88+
** Keep track of the maximun profit seen so far.
8989
- Return maxProfit.
9090

9191
[source, javascript]
9292
----
93-
include::interview-questions/buy-sell-stock.js[tag=description,solution]
93+
include::interview-questions/buy-sell-stock.js[tags=description;solution]
9494
----
9595

9696
The runtime is `O(n)` and the space complexity of `O(1)`.
@@ -118,14 +118,14 @@ Another case to take into consideration is that lists might have different lengt
118118

119119
- Have a pointer for each list
120120
- While there's a pointer that is not null, visite them
121-
- Compare each list node's value and take the smaller one.
122-
- Advance the pointer of the taken node to the next one.
121+
** Compare each list node's value and take the smaller one.
122+
** Advance the pointer of the taken node to the next one.
123123

124124
*Implementation*:
125125

126126
[source, javascript]
127127
----
128-
include::interview-questions/merge-lists.js[tag=description,solution]
128+
include::interview-questions/merge-lists.js[tags=description;solution]
129129
----
130130

131131
Notice that we used a "dummy" node or "sentinel node" to have some starting point for the final list.
@@ -163,14 +163,14 @@ A better way to solve this problem is iterating over each character on both list
163163

164164
- Set a pointer to iterate over each node in the lists.
165165
- For each node, have an index (starting at zero) and compare if both lists have the same data.
166-
- When the index reaches the last character on the current node, we move to the next node.
167-
- If we found that a character from one list doesn't match the other, we return `false`.
166+
** When the index reaches the last character on the current node, we move to the next node.
167+
** If we found that a character from one list doesn't match the other, we return `false`.
168168

169169
*Implementation*:
170170

171171
[source, javascript]
172172
----
173-
include::interview-questions/linkedlist-same-data.js[tag=description,solution]
173+
include::interview-questions/linkedlist-same-data.js[tags=description;solution]
174174
----
175175

176176
The function `findNextPointerIndex` is a helper to navigate each character on a linked list.
@@ -206,8 +206,8 @@ This is a parsing problem, and usually, stacks are good candidates for them.
206206

207207
- Create a mapping for each opening bracket to its closing counterpart.
208208
- Iterate through the string
209-
- When we found an opening bracket, insert the corresponding closing bracket into the stack.
210-
- When we found a closing bracket, pop from the stack and make sure it corresponds to the current character.
209+
** When we found an opening bracket, insert the corresponding closing bracket into the stack.
210+
** When we found a closing bracket, pop from the stack and make sure it corresponds to the current character.
211211
- Check the stack is empty. If there's a leftover, it means that something didn't close properly.
212212

213213
*Implementation*:
@@ -242,10 +242,10 @@ Here's an idea: start backward, so we know when there's a warmer temperature bef
242242
*Algorithm*:
243243

244244
- Traverse the daily temperatures backward
245-
- Push each temperature to a stack.
246-
- While the current temperature is larger than the one at the top of the stack, pop it.
247-
- If the stack is empty, then there's no warmer weather ahead, so it's 0.
248-
- If the stack has an element, calculate the index delta.
245+
** Push each temperature to a stack.
246+
** While the current temperature is larger than the one at the top of the stack, pop it.
247+
** If the stack is empty, then there's no warmer weather ahead, so it's 0.
248+
** If the stack has an element, calculate the index delta.
249249

250250
*Implementation*:
251251

@@ -275,7 +275,7 @@ The stack contains the indexes rather than the temperatures themselves.
275275
[#queue-q-recent-counter]
276276
include::content/part02/queue.asc[tag=queue-q-recent-counter]
277277

278-
We are asked to keep track of the request's count only within a given time window. A queue is a perfect application for this. We can add any new request to the Queue. Also, we need to check if the oldest element is outside the time window. If so, we remove it from the queue.
278+
We are asked to keep track of the request's count only within a given time window. A queue is a perfect application for this. We can add any new request to the Queue. Also, we need to check if the oldest element is outside the time window. If so, we remove it from the queue.
279279

280280
*Algorithm*:
281281

@@ -288,7 +288,7 @@ We are asked to keep track of the request's count only within a given time windo
288288

289289
[source, javascript]
290290
----
291-
include::interview-questions/recent-counter.js[tag=description,solution]
291+
include::interview-questions/recent-counter.js[tags=description;solution]
292292
----
293293

294294
Notice that we enqueue every request, and then we check all the ones that have "expire" and remove them from the queue.
@@ -311,27 +311,122 @@ This game is perfect to practice working with Queues. There are at least two opp
311311
- If the new location has food, remove that eaten food from its queue and place the next food on the map (if any).
312312
- If the new position doesn't have food, remove the tail of the snake since it moved.
313313
- If the snake new position hits itself, game over (return -1). To make this check, we have 2 options:
314-
- Queue: we can visit all the elements on the snake's queue (body) and check if a new position collides. That's `O(n)`
315-
- Set: we can maintain a `set` with all the snake locations, so the check is `O(1)`.
314+
** Queue: we can visit all the elements on the snake's queue (body) and check if a new position collides. That's `O(n)`
315+
** Set: we can maintain a `set` with all the snake locations, so the check is `O(1)`.
316316
- Move the snake's head to a new location (enqueue)
317317
- Return the score (snake's length - 1);
318318

319319
*Implementation*:
320320

321321
[source, javascript]
322322
----
323-
include::interview-questions/design-snake-game.js[tag=description,solution]
323+
include::interview-questions/design-snake-game.js[tags=description;solution]
324324
----
325325

326326
As you can see, we opted for using a set to trade speed for memory.
327327

328328
*Complexity Analysis*:
329329

330-
- Time: `O(1)`. Insert/Remove from Queue is constant time. Check for body collisions is `O(1)` when using a set. If instead of a set, you traversed the snake's queue to find a collision, it would be `O(n)`. Here`n` is the snake's max length, which is the size of the screen (height x width).
330+
- Time: `O(1)`. Insert/Remove from Queue is constant time. Check for body collisions is `O(1)` when using a set. If instead of a set, you traversed the snake's queue to find a collision, it would be `O(n)`. Here`n` is the snake's max length, which is the screen size (height x width).
331331
- Space: `O(n + m)`. `m` is the number of food items, and `n` is the snake's maximum size (height x width).
332332

333333

334334

335+
:leveloffset: +1
336+
337+
=== Solutions for Binary Tree Questions
338+
(((Interview Questions Solutions, Binary Tree)))
339+
340+
:leveloffset: -1
341+
342+
[#binary-tree-q-diameter-of-binary-tree]
343+
include::content/part03/binary-search-tree-traversal.asc[tag=binary-tree-q-diameter-of-binary-tree]
344+
345+
We are asked to find the longest path on a binary tree that might or might not pass through the root node.
346+
347+
We can calculate the height (distance from root to farthest leaf) of a binary tree using this recursive function:
348+
349+
[source, javascript]
350+
----
351+
function getHeight(node) {
352+
if (!node) return 0;
353+
const leftHeight = getHeight(node.left);
354+
const rightHeight = getHeight(node.right);
355+
return 1 + Math.max(leftHeight, rightHeight);
356+
}
357+
----
358+
359+
That function will give us the height from the furthest leaf to the root. However, the problem says that it might or might not go through the root.
360+
In that case, we can keep track of the maximum distance (`leftHeight + rightHeight`) seen so far.
361+
362+
*Algorithm*:
363+
364+
- Initialize diameter to `0`
365+
- Recursively find the height of the tree from the root.
366+
- Keep track of the maximum diameter length seen so far (left + right).
367+
- Return the diameter.
368+
369+
*Implementation*:
370+
371+
[source, javascript]
372+
----
373+
include::interview-questions/diameter-of-binary-tree.js[tags=description;solution]
374+
----
375+
376+
We are using `Math.max` to keep track of the longest diameter seen.
377+
378+
*Complexity Analysis*:
379+
380+
- Time: `O(n)`, where `n` is each of the tree nodes. We visite each one once.
381+
- Space: `O(n)`. We use `O(1)` variables, but because we are using the `height` recursive function, we use the implicit call stack, thus `O(n)`.
382+
383+
384+
385+
386+
[#binary-tree-q-binary-tree-right-side-view]
387+
include::content/part03/binary-search-tree-traversal.asc[tag=binary-tree-q-binary-tree-right-side-view]
388+
389+
The first thing that might come to mind when you have to visit a tree, level by level, is BFS.
390+
We can visit the tree using a Queue and keep track when a level ends, and the new one starts.
391+
392+
Since during BFS, we dequeue one node and enqueue their two children (left and right), we might have two levels (current and next one). For this problem, we need to know what the last node on the current level is.
393+
394+
.There are several ways to solve this problem using BFS. Here are some ideas:
395+
- *1 Queue + Sentinel node*: we can use a special character in the `Queue` like `'*'` or `null` to indicate the level's change. So, we would start something like this `const queue = new Queue([root, '*']);`.
396+
- *2 Queues*: using a "special" character might be seen as hacky, so you can also opt to keep two queues: one for the current level and another for the next level.
397+
- *1 Queue + size tracking*: we track the Queue's `size` before the children are enqueued. That way, we know where the current level ends.
398+
399+
We are going to implement BFS with "1 Queue + size tracking", since it's arguably the most elegant.
400+
401+
*Algorithm*:
402+
403+
- Enqueue root
404+
- While the queue has an element
405+
** Check the current size of the queue
406+
** Dequeue only `size` times, and for each dequeued node, enqueue their children.
407+
** Check if the node is the last one in its level and add it to the answer.
408+
409+
*Implementation*:
410+
411+
[source, javascript]
412+
----
413+
include::interview-questions/binary-tree-right-side-view.js[tags=description;solution]
414+
----
415+
416+
This problem is also possible to be solved using DFS. The trick is to start with the right child and add it to the solution when it is the first one on its level.
417+
418+
[source, javascript]
419+
----
420+
include::interview-questions/binary-tree-right-side-view.js[tag=dfs]
421+
----
422+
423+
The complexity of any of the BFS methods or DFS is similar.
424+
425+
*Complexity Analysis*:
426+
427+
- Time: `O(n)`. We visit every node, once.
428+
- Space: `O(n)`. For BFS, the worst-case space is given by the maximum *width*. That is when the binary tree is complete so that the last level would have `(n-1)/2` nodes, thus `O(n)`. For the DFS, the space complexity will be given by the tree's maximum *height*. In the worst-case, the binary tree is skewed to the right so that we will have an implicit call stack of size `n`.
429+
335430

336431
// [#linkedlist-q-FILENAME]
337432
// include::content/part02/linked-list.asc[tag=linkedlist-q-FILENAME]

‎book/config

‎book/content/part02/array.asc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ maxSubArray([-3, 4,-1, 2, 1, -5]); // 6 (sum [4,-1, 2, 1])
293293
maxSubArray([-2, 1, -3, 4, -1, 3, 1]); // 7 (sum [4,-1, 3, 1])
294294
----
295295

296-
_Seen in interviews at: Amazon, Apple, Google, Microsoft, Facebook_
296+
// _Seen in interviews at: Amazon, Apple, Google, Microsoft, Facebook_
297297
// end::array-q-max-subarray[]
298298

299299
[source, javascript]
@@ -320,7 +320,7 @@ maxProfit([5, 10, 5, 10]) // 5 (buying at 5 and selling at 10)
320320
321321
----
322322

323-
_Seen in interviews at: Amazon, Facebook, Bloomberg_
323+
// _Seen in interviews at: Amazon, Facebook, Bloomberg_
324324
// end::array-q-buy-sell-stock[]
325325

326326
[source, javascript]

‎book/content/part02/linked-list.asc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ mergeTwoLists(2->3->4, 1->2); // 1->2->2->3->4
301301
mergeTwoLists(2->3->4,null); // 2->3->4
302302
----
303303

304-
_Seen in interviews at: Amazon, Adobe, Microsoft, Google_
304+
// _Seen in interviews at: Amazon, Adobe, Microsoft, Google_
305305
// end::linkedlist-q-merge-lists[]
306306

307307
[source, javascript]
@@ -329,7 +329,7 @@ hasSameData(hello, hel->lo); // true
329329
hasSameData(he->ll->o, h->i); // false
330330
----
331331

332-
_Seen in interviews at: Facebook_
332+
// _Seen in interviews at: Facebook_
333333
// end::linkedlist-q-linkedlist-same-data[]
334334

335335
[source, javascript]

‎book/content/part02/queue.asc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ counter.request(3100); // 1 (last requests was 100 ms ago, > 10ms, so doesn't co
103103
counter.request(3105); // 2 (last requests was 5 ms ago, <= 10ms, so it counts)
104104
----
105105

106-
_Seen in interviews at: Google, Bloomberg, Yandex_
106+
// _Seen in interviews at: Google, Bloomberg, Yandex_
107107
// end::queue-q-recent-counter[]
108108

109109

@@ -135,7 +135,7 @@ expect(snakeGame.move('L')).toEqual(2); // 2 (ate food2)
135135
expect(snakeGame.move('U')).toEqual(-1); // -1 (hit wall)
136136
----
137137

138-
_Seen in interviews at: Amazon, Bloomberg, Apple_
138+
// _Seen in interviews at: Amazon, Bloomberg, Apple_
139139
// end::queue-q-design-snake-game[]
140140

141141
[source, javascript]

‎book/content/part02/stack.asc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ isParenthesesValid('[{]}'); // false (brakets are not closed in the right order)
106106
isParenthesesValid('([{)}]'); // false (closing is out of order)
107107
----
108108

109-
_Seen in interviews at: Amazon, Bloomberg, Facebook, Citadel_
109+
// _Seen in interviews at: Amazon, Bloomberg, Facebook, Citadel_
110110
// end::stack-q-valid-parentheses[]
111111

112112
[source, javascript]
@@ -135,7 +135,7 @@ dailyTemperatures([30, 28, 50, 40, 30]); // [2 (to 50), 1 (to 28), 0, 0, 0]
135135
dailyTemperatures([73, 69, 72, 76, 73, 100]); // [3, 1, 1, 0, 1, 100]
136136
----
137137

138-
_Seen in interviews at: Amazon, Adobe, Cisco_
138+
// _Seen in interviews at: Amazon, Adobe, Cisco_
139139
// end::stack-q-daily-temperatures[]
140140

141141
[source, javascript]

‎book/content/part03/binary-search-tree-traversal.asc

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,141 @@ If we have the following tree:
9292
----
9393

9494
Post-order traverval will return `3, 4, 5, 15, 40, 30, 10`.
95+
96+
97+
==== Practice Questions
98+
(((Interview Questions, Binary Tree)))
99+
100+
101+
// tag::binary-tree-q-diameter-of-binary-tree[]
102+
===== Binary Tree Diameter
103+
104+
*BT-1*) _Find the diameter of a binary tree. A tree's diameter is the longest possible path from two nodes (it doesn't need to include the root). The length of a diameter is calculated by counting the number of edges on the path._
105+
106+
// end::binary-tree-q-diameter-of-binary-tree[]
107+
108+
// _Seen in interviews at: Facebook, Amazon, Google_
109+
110+
// Example 1:
111+
// [graphviz, tree-diameter-example-1, png]
112+
// ....
113+
// graph G {
114+
// 1 -- 2 [color=red]
115+
// 1 -- 3 [color=red]
116+
117+
// 2 -- 4
118+
// 2 -- 5 [color=red]
119+
// }
120+
// ....
121+
122+
// Example 2:
123+
// [graphviz, tree-diameter-example-2, png]
124+
// ....
125+
// graph G {
126+
// 1
127+
// 2
128+
// 3
129+
// 4
130+
// 5
131+
// 6
132+
// "null" [color=white, fontcolor = white]
133+
// "null." [color=white, fontcolor = white]
134+
// 7
135+
// 8
136+
// "null.." [color=white, fontcolor = white]
137+
// "null..." [color=white, fontcolor = white]
138+
// 9
139+
140+
// 1 -- 2
141+
// 1 -- 3
142+
143+
// 3 -- 4 [color=red]
144+
// 3 -- 5 [color=red]
145+
146+
// 4 -- 6 [color=red]
147+
// 4 -- "null." [color=white]
148+
149+
// 5 -- "null" [color=white]
150+
// 5 -- 7 [color=red]
151+
152+
// 6 -- 8 [color=red]
153+
// 6 -- "null.." [color=white]
154+
155+
// 7 -- "null..." [color=white]
156+
// 7 -- 9 [color=red]
157+
// }
158+
// ....
159+
160+
Examples:
161+
162+
[source, javascript]
163+
----
164+
/* 1
165+
/ \
166+
2 3
167+
/ \
168+
4 5 */
169+
diameterOfBinaryTree(toBinaryTree([1,2,3,4,5])); // 3
170+
// For len 3, the path could be 3-1-2-5 or 3-1-2-4
171+
172+
/* 1
173+
/ \
174+
2 3
175+
/ \
176+
4 5
177+
/ \
178+
6 7
179+
/ \
180+
8 9 */
181+
const array = [1,2,3,null,null,4,5,6,null,null,7,8,null,null,9];
182+
const tree = BinaryTreeNode.from(array);
183+
diameterOfBinaryTree(tree); // 6 (path: 8-6-4-3-5-7-9)
184+
----
185+
186+
Starter code:
187+
188+
[source, javascript]
189+
----
190+
include::../../interview-questions/diameter-of-binary-tree.js[tags=description;placeholder]
191+
----
192+
193+
194+
_Solution: <<binary-tree-q-diameter-of-binary-tree>>_
195+
196+
197+
198+
199+
// tag::binary-tree-q-binary-tree-right-side-view[]
200+
===== Binary Tree from right side view
201+
202+
*BT-2*) _Imagine that you are viewing the tree from the right side. What nodes would you see?_
203+
204+
// end::binary-tree-q-binary-tree-right-side-view[]
205+
206+
// _Seen in interviews at: Facebook, Amazon, ByteDance (TikTok)._
207+
208+
Examples:
209+
210+
[source, javascript]
211+
----
212+
/*
213+
1 <- 1 (only node on level)
214+
/ \
215+
2 3 <- 3 (3 is the rightmost)
216+
\
217+
4 <- 4 (only node on level) */
218+
rightSideView(BinaryTreeNode.from([1, 2, 3, null, 4])); // [1, 3, 4]
219+
220+
rightSideView(BinaryTreeNode.from([])); // []
221+
rightSideView(BinaryTreeNode.from([1, 2, 3, null, 5, null, 4, 6])); // [1, 3, 4, 6]
222+
----
223+
224+
Starter code:
225+
226+
[source, javascript]
227+
----
228+
include::../../interview-questions/binary-tree-right-side-view.js[tags=description;placeholder]
229+
----
230+
231+
_Solution: <<binary-tree-q-binary-tree-right-side-view>>_
232+

‎book/content/part03/tree-intro.asc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ A tree is a non-linear data structure where a node can have zero or more connect
1212
.Tree Data Structure: root node and descendants.
1313
image::image31.jpg[image,width=404,height=240]
1414

15-
As you can see in the picture above, this data structure resembles an inverted tree hence the name. It starts with a *root* node and *branch* off with its descendants, and finally *leaves*.
15+
As you can see in the picture above, this data structure resembles an inverted tree, hence the name. It starts with a *root* node and *branch* off with its descendants, and finally *leaves*.
1616

1717
==== Implementing a Tree
1818

@@ -54,17 +54,17 @@ image::image31.jpg[image]
5454

5555
==== Types of Binary Trees
5656

57-
There are different kinds of trees depending on the restrictions. E.g. The trees that have two children or less are called *binary tree*, while trees with at most three children are called *Ternary Tree*. Since binary trees are most common we are going to cover them here and others in another chapter.
57+
There are different kinds of trees, depending on the restrictions. E.g. The trees with two children or less are called *binary tree*, while trees with at most three children are called *Ternary Tree*. Since binary trees are the most common, we will cover them here and others in another chapter.
5858

5959
===== Binary Tree
6060
(((Binary Tree)))
6161
(((Data Structures, Non-Linear, Binary Tree)))
62-
The binary restricts the nodes to have at most two children. Trees, in general, can have 3, 4, 23 or more, but not binary trees.
62+
The binary restricts the nodes to have at most two children. Trees can have 0, 1, 2, 7, or more, but not binary trees.
6363

6464
.Binary tree has at most 2 children while non-binary trees can have more.
6565
image::image32.png[image,width=321,height=193]
6666

67-
Binary trees are one of the most used kinds of tree, and they are used to build other data structures.
67+
Binary trees are one of the most used kinds of trees, and they are used to build other data structures.
6868

6969
.Binary Tree Applications
7070
- <<part03-graph-data-structures#map>>
@@ -90,7 +90,7 @@ image::image33.png[image,width=348,height=189]
9090
(((Max-Heap)))
9191
(((Min-Heap)))
9292
(((Data Structures, Non-Linear, Binary Heap)))
93-
The heap (max-heap) is a type of binary tree where the parent's value is higher than the value of both children. Opposed to the BST, the left child doesn’t have to be smaller than the right child.
93+
The heap (max-heap) is a type of binary tree where the parent's value is higher than both children's value. Opposed to the BST, the left child doesn’t have to be smaller than the right child.
9494

9595
.Heap vs BST
9696
image::image34.png[image,width=325,height=176]
10.7 KB
Loading
22.3 KB
Loading
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const { Queue } = require('../../src/index');
2+
3+
// tag::description[]
4+
/**
5+
* Find the rightmost nodes by level.
6+
*
7+
* @example
8+
* 1 <- 1
9+
* / \
10+
* 2 3 <- 3
11+
* \
12+
* 4 <- 4
13+
*
14+
* @param {BinaryTreeNode} root - The root of the binary tree.
15+
* @returns {number[]} - array with the rightmost nodes values.
16+
*/
17+
function rightSideView(root) {
18+
// end::description[]
19+
// tag::placeholder[]
20+
// write your code here...
21+
// end::placeholder[]
22+
// tag::solution[]
23+
if (!root) return [];
24+
const queue = new Queue([root]);
25+
const ans = [];
26+
27+
while (queue.size) {
28+
const { size } = queue;
29+
for (let i = 0; i < size; i++) {
30+
const node = queue.dequeue();
31+
if (i === size - 1) ans.push(node.value);
32+
if (node.left) queue.enqueue(node.left);
33+
if (node.right) queue.enqueue(node.right);
34+
}
35+
}
36+
37+
return ans;
38+
// end::solution[]
39+
// tag::description[]
40+
}
41+
// end::description[]
42+
43+
// tag::dfs[]
44+
function rightSideViewDfs(root) {
45+
const ans = [];
46+
47+
const dfs = (node, level = 0) => {
48+
if (!node) return;
49+
if (level === ans.length) ans.push(node.value);
50+
dfs(node.right, level + 1); // right side first!
51+
dfs(node.left, level + 1);
52+
};
53+
54+
dfs(root);
55+
return ans;
56+
}
57+
// end::dfs[]
58+
59+
module.exports = { rightSideView, rightSideViewDfs };
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const { rightSideView, rightSideViewDfs } = require('./binary-tree-right-side-view');
2+
const { BinaryTreeNode } = require('../../src/index');
3+
4+
[rightSideView, rightSideViewDfs].forEach((fn) => {
5+
describe(`Binary Tree: ${fn.name}`, () => {
6+
it('should work with null', () => {
7+
const actual = null;
8+
const expected = [];
9+
expect(fn(actual)).toEqual(expected);
10+
});
11+
12+
it('should work with small case', () => {
13+
const actual = BinaryTreeNode.from([1, 2, 3, null, 4]);
14+
const expected = [1, 3, 4];
15+
expect(fn(actual)).toEqual(expected);
16+
});
17+
18+
it('should work with other case', () => {
19+
const actual = BinaryTreeNode.from([1, 2, 3, null, 5, null, 4, 6]);
20+
const expected = [1, 3, 4, 6];
21+
expect(fn(actual)).toEqual(expected);
22+
});
23+
});
24+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// tag::description[]
2+
/**
3+
* Find the length of the binary tree diameter.
4+
*
5+
* @param {BinaryTreeNode | null} root - Binary Tree's root.
6+
* @returns {number} tree's diameter (longest possible path on the tree)
7+
*/
8+
function diameterOfBinaryTree(root) {
9+
// end::description[]
10+
// tag::placeholder[]
11+
// write your code here...
12+
// end::placeholder[]
13+
// tag::solution[]
14+
let diameter = 0;
15+
16+
const height = (node) => {
17+
if (!node) return 0;
18+
const left = height(node.left);
19+
const right = height(node.right);
20+
diameter = Math.max(diameter, left + right);
21+
return 1 + Math.max(left, right);
22+
};
23+
24+
height(root);
25+
return diameter;
26+
// end::solution[]
27+
// tag::description[]
28+
}
29+
// end::description[]
30+
31+
module.exports = { diameterOfBinaryTree };
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const { diameterOfBinaryTree } = require('./diameter-of-binary-tree');
2+
const { BinaryTreeNode } = require('../../src/index');
3+
4+
describe('Binary Tree: Diameter', () => {
5+
it('should find the diameter', () => {
6+
const actual = BinaryTreeNode.from([1, 2, 3, 4, 5]);
7+
expect(diameterOfBinaryTree(actual)).toEqual(3);
8+
});
9+
10+
it('should find the diameter when does not pass through the root node', () => {
11+
const arr = [1, 2, 3, null, null, 4, 5, 6, null, null, 7, 8, null, null, 9];
12+
expect(diameterOfBinaryTree(BinaryTreeNode.from(arr))).toEqual(6);
13+
});
14+
});

‎package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
],
1515
"scripts": {
1616
"test": "jest --verbose",
17-
"watch": "jest --watch --verbose --coverage",
17+
"watch": "jest --watch --coverage",
1818
"ci": "npx eslint src/ && jest --coverage",
1919
"coverage": "jest --coverage && open coverage/lcov-report/index.html",
2020
"coverage:win": "jest --coverage && cmd.exe /C start coverage/lcov-report/index.html",

‎src/data-structures/trees/binary-tree-node.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,21 @@ class BinaryTreeNode {
172172
this.meta.data = value;
173173
return this;
174174
}
175+
176+
/**
177+
* Convert Binary tree from an iterable (e.g. array)
178+
* @param {array|string} iterable - The iterable
179+
*/
180+
static from(iterable = []) {
181+
const toBinaryTree = (array, index = 0) => {
182+
if (index >= array.length) return null;
183+
const node = new BinaryTreeNode(array[index]);
184+
node.setLeftAndUpdateParent(toBinaryTree(array, index * 2 + 1));
185+
node.setRightAndUpdateParent(toBinaryTree(array, index * 2 + 2));
186+
return node;
187+
};
188+
return toBinaryTree(Array.from(iterable));
189+
}
175190
}
176191

177192
BinaryTreeNode.RIGHT = RIGHT;
Lines changed: 141 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,167 @@
11
const { BinaryTreeNode } = require('../../index');
22

3+
const { LEFT, RIGHT } = BinaryTreeNode;
4+
35
describe('Binary Tree Node', () => {
46
let treeNode;
57

6-
beforeEach(() => {
7-
treeNode = new BinaryTreeNode('hola');
8-
});
9-
10-
it('should start with null parent', () => {
11-
expect(treeNode.parent).toBe(null);
12-
});
13-
14-
it('should start with empty metadata', () => {
15-
expect(treeNode.meta).toEqual({});
16-
});
17-
18-
it('should hold a value', () => {
19-
expect(treeNode.value).toBe('hola');
20-
});
21-
22-
it('should have a height 0', () => {
23-
expect(treeNode.height).toBe(0);
24-
});
25-
26-
it('should set/get left node', () => {
27-
expect(treeNode.left).toBe(null);
28-
const newNode = new BinaryTreeNode(1);
29-
treeNode.setLeftAndUpdateParent(newNode);
30-
expect(treeNode.left.value).toBe(1);
31-
32-
expect(newNode.parent).toBe(treeNode);
33-
expect(treeNode.height).toBe(1);
34-
expect(treeNode.balanceFactor).toBe(1);
35-
});
36-
37-
it('should set/get right node', () => {
38-
expect(treeNode.right).toBe(null);
39-
const newNode = new BinaryTreeNode(1);
40-
treeNode.setRightAndUpdateParent(newNode);
41-
42-
expect(treeNode.right.value).toBe(1);
43-
expect(newNode.parent).toBe(treeNode);
44-
expect(treeNode.height).toBe(1);
45-
expect(treeNode.balanceFactor).toBe(-1);
46-
});
47-
48-
describe('Family operations', () => {
49-
let g;
50-
let p;
51-
let u;
52-
let c;
53-
let s;
54-
8+
describe('with instance', () => {
559
beforeEach(() => {
56-
g = new BinaryTreeNode('grandparent');
57-
p = new BinaryTreeNode('parent');
58-
u = new BinaryTreeNode('uncle');
59-
c = new BinaryTreeNode('child');
60-
s = new BinaryTreeNode('sibling');
61-
62-
g.setRightAndUpdateParent(p);
63-
g.setLeftAndUpdateParent(u);
64-
p.setRightAndUpdateParent(c);
65-
p.setLeftAndUpdateParent(s);
10+
treeNode = new BinaryTreeNode('hola');
6611
});
6712

68-
it('should set heights', () => {
69-
expect(g.height).toBe(2);
70-
expect(g.balanceFactor).toBe(-1);
71-
72-
expect(p.height).toBe(1);
73-
expect(p.balanceFactor).toBe(0);
13+
it('should start with null parent', () => {
14+
expect(treeNode.parent).toBe(null);
15+
});
7416

75-
expect(u.height).toBe(0);
76-
expect(u.balanceFactor).toBe(0);
17+
it('should start with empty metadata', () => {
18+
expect(treeNode.meta).toEqual({});
7719
});
7820

79-
it('should get the sibling', () => {
80-
expect(c.sibling).toBe(s);
81-
expect(p.sibling).toBe(u);
21+
it('should hold a value', () => {
22+
expect(treeNode.value).toBe('hola');
8223
});
8324

84-
it('should set leaf correctly', () => {
85-
expect(c.isLeaf).toBe(true);
86-
expect(u.isLeaf).toBe(true);
87-
expect(p.isLeaf).toBe(false);
88-
expect(g.isLeaf).toBe(false);
25+
it('should have a height 0', () => {
26+
expect(treeNode.height).toBe(0);
8927
});
9028

91-
it('should get null if no sibling', () => {
92-
expect(g.sibling).toBe(null);
29+
it('should set/get left node', () => {
30+
expect(treeNode.left).toBe(null);
31+
const newNode = new BinaryTreeNode(1);
32+
treeNode.setLeftAndUpdateParent(newNode);
33+
expect(treeNode.left.value).toBe(1);
34+
35+
expect(newNode.parent).toBe(treeNode);
36+
expect(treeNode.height).toBe(1);
37+
expect(treeNode.balanceFactor).toBe(1);
9338
});
9439

95-
it('should get the uncle', () => {
96-
expect(c.uncle).toBe(u);
40+
it('should set/get right node', () => {
41+
expect(treeNode.right).toBe(null);
42+
const newNode = new BinaryTreeNode(1);
43+
treeNode.setRightAndUpdateParent(newNode);
44+
45+
expect(treeNode.right.value).toBe(1);
46+
expect(newNode.parent).toBe(treeNode);
47+
expect(treeNode.height).toBe(1);
48+
expect(treeNode.balanceFactor).toBe(-1);
9749
});
9850

99-
it('should get null if no uncle', () => {
100-
expect(g.uncle).toBe(null);
101-
expect(p.uncle).toBe(null);
51+
describe('Family operations', () => {
52+
let g;
53+
let p;
54+
let u;
55+
let c;
56+
let s;
57+
58+
beforeEach(() => {
59+
g = new BinaryTreeNode('grandparent');
60+
p = new BinaryTreeNode('parent');
61+
u = new BinaryTreeNode('uncle');
62+
c = new BinaryTreeNode('child');
63+
s = new BinaryTreeNode('sibling');
64+
65+
g.setRightAndUpdateParent(p);
66+
g.setLeftAndUpdateParent(u);
67+
p.setRightAndUpdateParent(c);
68+
p.setLeftAndUpdateParent(s);
69+
});
70+
71+
it('should set heights', () => {
72+
expect(g.height).toBe(2);
73+
expect(g.balanceFactor).toBe(-1);
74+
75+
expect(p.height).toBe(1);
76+
expect(p.balanceFactor).toBe(0);
77+
78+
expect(u.height).toBe(0);
79+
expect(u.balanceFactor).toBe(0);
80+
});
81+
82+
it('should get the sibling', () => {
83+
expect(c.sibling).toBe(s);
84+
expect(p.sibling).toBe(u);
85+
});
86+
87+
it('should set leaf correctly', () => {
88+
expect(c.isLeaf).toBe(true);
89+
expect(u.isLeaf).toBe(true);
90+
expect(p.isLeaf).toBe(false);
91+
expect(g.isLeaf).toBe(false);
92+
});
93+
94+
it('should get null if no sibling', () => {
95+
expect(g.sibling).toBe(null);
96+
});
97+
98+
it('should get the uncle', () => {
99+
expect(c.uncle).toBe(u);
100+
});
101+
102+
it('should get null if no uncle', () => {
103+
expect(g.uncle).toBe(null);
104+
expect(p.uncle).toBe(null);
105+
});
106+
107+
it('true if is parent left child', () => {
108+
expect(s.isParentLeftChild).toBe(true);
109+
expect(s.isParentRightChild).toBe(false);
110+
});
111+
112+
it('true if is parent left child', () => {
113+
expect(c.isParentLeftChild).toBe(false);
114+
expect(c.isParentRightChild).toBe(true);
115+
});
102116
});
117+
});
103118

104-
it('true if is parent left child', () => {
105-
expect(s.isParentLeftChild).toBe(true);
106-
expect(s.isParentRightChild).toBe(false);
119+
describe('with static methods', () => {
120+
it('should work with null', () => {
121+
const tree = BinaryTreeNode.from();
122+
expect(tree).toEqual(null);
107123
});
108124

109-
it('true if is parent left child', () => {
110-
expect(c.isParentLeftChild).toBe(false);
111-
expect(c.isParentRightChild).toBe(true);
125+
it('should build from array', () => {
126+
/*
127+
0
128+
/ \
129+
1 2
130+
/ \ \
131+
3 5 4
132+
*/
133+
const tree = BinaryTreeNode.from([0, 1, 2, 3, 5, null, 4]);
134+
expect(tree.toValues()).toEqual({
135+
value: 0,
136+
left: 1,
137+
right: 2,
138+
parent: null,
139+
parentSide: null,
140+
});
141+
142+
expect(tree.left.toValues()).toEqual({
143+
value: 1,
144+
left: 3,
145+
right: 5,
146+
parent: 0,
147+
parentSide: LEFT,
148+
});
149+
150+
expect(tree.right.toValues()).toEqual({
151+
value: 2,
152+
left: null,
153+
right: 4,
154+
parent: 0,
155+
parentSide: RIGHT,
156+
});
157+
158+
expect(tree.right.right.toValues()).toEqual({
159+
value: 4,
160+
left: null,
161+
right: null,
162+
parent: 2,
163+
parentSide: RIGHT,
164+
});
112165
});
113166
});
114167
});

0 commit comments

Comments
 (0)
Please sign in to comment.