Skip to content

Commit 40d6b64

Browse files
authoredAug 31, 2020
Merge pull request amejiarosario#72 from amejiarosario/feat/queue-interview-questions
Feat/queue interview questions
2 parents 263c9dc + c1a8f8e commit 40d6b64

15 files changed

+481
-51
lines changed
 

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

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ include::interview-questions/max-subarray.js[tag=description]
4747
include::interview-questions/max-subarray.js[tag=solution]
4848
----
4949

50-
The runtime is `O(n)` and space complexity of `O(1)`.
50+
The runtime is `O(n)` and the space complexity of `O(1)`.
5151

5252

5353

@@ -90,11 +90,10 @@ Algorithm:
9090

9191
[source, javascript]
9292
----
93-
include::interview-questions/buy-sell-stock.js[tag=description]
94-
include::interview-questions/buy-sell-stock.js[tag=solution]
93+
include::interview-questions/buy-sell-stock.js[tag=description,solution]
9594
----
9695

97-
The runtime is `O(n)` and space complexity of `O(1)`.
96+
The runtime is `O(n)` and the space complexity of `O(1)`.
9897

9998

10099

@@ -126,8 +125,7 @@ Another case to take into consideration is that lists might have different lengt
126125

127126
[source, javascript]
128127
----
129-
include::interview-questions/merge-lists.js[tag=description]
130-
include::interview-questions/merge-lists.js[tag=solution]
128+
include::interview-questions/merge-lists.js[tag=description,solution]
131129
----
132130

133131
Notice that we used a "dummy" node or "sentinel node" to have some starting point for the final list.
@@ -159,7 +157,7 @@ include::interview-questions/linkedlist-same-data.js[tag=hasSameDataBrute1]
159157

160158
Notice that the problem mentions that lists could be huge (millions of nodes). If the first character on each list is different, we are unnecessarily computing millions of nodes, when a straightforward check will do the job.
161159

162-
A better way to solve this problem is iterating over each character on both lists, and when we found mistmatch, we return `false` immediately. If they are the same, we still have to visit all of them.
160+
A better way to solve this problem is iterating over each character on both lists, and when we found a mismatch, we return `false` immediately. If they are the same, we still have to visit all of them.
163161

164162
*Algorithm*:
165163

@@ -172,12 +170,11 @@ A better way to solve this problem is iterating over each character on both list
172170

173171
[source, javascript]
174172
----
175-
include::interview-questions/linkedlist-same-data.js[tag=description]
176-
include::interview-questions/linkedlist-same-data.js[tag=solution]
173+
include::interview-questions/linkedlist-same-data.js[tag=description,solution]
177174
----
178175

179176
The function `findNextPointerIndex` is a helper to navigate each character on a linked list.
180-
Notice, that we increase the index (`i + 1`) on each iteration.
177+
Notice that we increase the index (`i + 1`) on each iteration.
181178
If the index overflows, it moves to the next node and reset the index to zero.
182179

183180

@@ -207,7 +204,7 @@ This is a parsing problem, and usually, stacks are good candidates for them.
207204

208205
*Algorithm*:
209206

210-
- Create a mapping for each opening bracket, to its closing counterpart.
207+
- Create a mapping for each opening bracket to its closing counterpart.
211208
- Iterate through the string
212209
- When we found an opening bracket, insert the corresponding closing bracket into the stack.
213210
- When we found a closing bracket, pop from the stack and make sure it corresponds to the current character.
@@ -267,6 +264,75 @@ The stack contains the indexes rather than the temperatures themselves.
267264

268265

269266

267+
:leveloffset: +1
268+
269+
=== Solutions for Queue Questions
270+
(((Interview Questions Solutions, Queue)))
271+
272+
:leveloffset: -1
273+
274+
275+
[#queue-q-recent-counter]
276+
include::content/part02/queue.asc[tag=queue-q-recent-counter]
277+
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.
279+
280+
*Algorithm*:
281+
282+
- Enqueue new requests.
283+
- Take a `peek` at the oldest request in the queue.
284+
- While `current timestamp - oldest timestamp`, dequeue the oldest.
285+
- Return the length of the queue.
286+
287+
*Implementation*:
288+
289+
[source, javascript]
290+
----
291+
include::interview-questions/recent-counter.js[tag=description,solution]
292+
----
293+
294+
Notice that we enqueue every request, and then we check all the ones that have "expire" and remove them from the queue.
295+
296+
*Complexity Analysis*:
297+
298+
- Time: `O(n)`, where `n` is the number of requests. One Enqueue/Dequeue operation is O(1). However, we might run into a worst-case where all requests have to be dequeued.
299+
- Space: `O(W)`, where `W` is the time window. We can have at most W requests in the queue since they are in increasing order without duplicates.
300+
301+
302+
[#queue-q-design-snake-game]
303+
include::content/part02/queue.asc[tag=queue-q-design-snake-game]
304+
305+
This game is perfect to practice working with Queues. There are at least two opportunities to use a Queue. You can enqueue the food location, and also you can keep the snake's body parts on a Queue. We insert a new position into the snake's queue on every move and dequeue the last location to indicate the snake moved. Every time the snake eats food, it grows one more unit. The food gets dequeue, and we place the next food location (if any).
306+
307+
*Algorithm*:
308+
309+
- Based on the snake's head current position, calculate the next location based on the given move `direction`.
310+
- If the new position is outside the boundaries, it's game over (return -1).
311+
- If the new location has food, remove that eaten food from its queue and place the next food on the map (if any).
312+
- If the new position doesn't have food, remove the tail of the snake since it moved.
313+
- 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)`.
316+
- Move the snake's head to a new location (enqueue)
317+
- Return the score (snake's length - 1);
318+
319+
*Implementation*:
320+
321+
[source, javascript]
322+
----
323+
include::interview-questions/design-snake-game.js[tag=description,solution]
324+
----
325+
326+
As you can see, we opted for using a set to trade speed for memory.
327+
328+
*Complexity Analysis*:
329+
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).
331+
- Space: `O(n + m)`. `m` is the number of food items, and `n` is the snake's maximum size (height x width).
332+
333+
334+
335+
270336
// [#linkedlist-q-FILENAME]
271337
// include::content/part02/linked-list.asc[tag=linkedlist-q-FILENAME]
272338

@@ -293,4 +359,3 @@ The stack contains the indexes rather than the temperatures themselves.
293359

294360
// - Time: `O(?)`. WHY?
295361
// - Space: `O(?)`. WHY?
296-

‎book/config

‎book/content/part02/array.asc

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,24 @@ To sum up, the time complexity of an array is:
276276
|===
277277
//end::table
278278

279-
==== Interview Questions
279+
==== Practice Questions
280280
(((Interview Questions, Arrays)))
281281

282282
// tag::array-q-max-subarray[]
283283
===== Max Subarray
284284

285285
*AR-1*) _Given an array of integers, find the maximum sum of consecutive elements (subarray)._
286+
287+
Examples:
288+
289+
[source, javascript]
290+
----
291+
maxSubArray([1, -3, 10, -5]); // 10 (taking only 10)
292+
maxSubArray([-3, 4,-1, 2, 1, -5]); // 6 (sum [4,-1, 2, 1])
293+
maxSubArray([-2, 1, -3, 4, -1, 3, 1]); // 7 (sum [4,-1, 3, 1])
294+
----
295+
296+
_Seen in interviews at: Amazon, Apple, Google, Microsoft, Facebook_
286297
// end::array-q-max-subarray[]
287298

288299
[source, javascript]
@@ -298,6 +309,18 @@ _Solution: <<array-q-max-subarray>>_
298309
===== Best Time to Buy and Sell an Stock
299310

300311
*AR-2*) _You are given an array of integers. Each value represents the closing value of the stock on that day. You are only given one chance to buy and then sell. What's the maximum profit you can obtain? (Note: you have to buy first and then sell)_
312+
313+
Examples:
314+
315+
[source, javascript]
316+
----
317+
maxProfit([1, 2, 3]) // 2 (buying at 1 and selling at 3)
318+
maxProfit([3, 2, 1]) // 2 (no buys)
319+
maxProfit([5, 10, 5, 10]) // 5 (buying at 5 and selling at 10)
320+
321+
----
322+
323+
_Seen in interviews at: Amazon, Facebook, Bloomberg_
301324
// end::array-q-buy-sell-stock[]
302325

303326
[source, javascript]

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,16 +286,22 @@ Use a doubly linked list when:
286286

287287
For the next two linear data structures <<part02-linear-data-structures#stack>> and <<part02-linear-data-structures#queue>>, we are going to use a doubly-linked list to implement them. We could use an array as well, but since inserting/deleting from the start performs better with linked-lists, we will use that.
288288

289-
==== Interview Questions
290-
(((Interview Questions, Arrays)))
291-
292-
293-
289+
==== Practice Questions
290+
(((Interview Questions, Linked Lists)))
294291

295292
// tag::linkedlist-q-merge-lists[]
296293
===== Merge Linked Lists into One
297294

298295
*LL-1*) _Merge two sorted lists into one (and keep them sorted)_
296+
297+
Examples:
298+
299+
----
300+
mergeTwoLists(2->3->4, 1->2); // 1->2->2->3->4
301+
mergeTwoLists(2->3->4,null); // 2->3->4
302+
----
303+
304+
_Seen in interviews at: Amazon, Adobe, Microsoft, Google_
299305
// end::linkedlist-q-merge-lists[]
300306

301307
[source, javascript]
@@ -313,7 +319,17 @@ _Solution: <<linkedlist-q-merge-lists>>_
313319
// tag::linkedlist-q-linkedlist-same-data[]
314320
===== Check if two strings lists are the same
315321

316-
*LL-2*) _Given two linked lists with strings, check if are the same_
322+
*LL-2*) _Given two linked lists with strings, check if the data is equivalent._
323+
324+
Examples:
325+
326+
----
327+
hasSameData(he->ll->o, hel->lo); // true
328+
hasSameData(hello, hel->lo); // true
329+
hasSameData(he->ll->o, h->i); // false
330+
----
331+
332+
_Seen in interviews at: Facebook_
317333
// end::linkedlist-q-linkedlist-same-data[]
318334

319335
[source, javascript]

‎book/content/part02/queue.asc

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ A queue is a linear data structure where the data flows in a *First-In-First-Out
1414
.Queue data structure is like a line of people: the First-in, is the First-out
1515
image::image30.png[image,width=528,height=171]
1616

17-
A queue is like a line of people at the bank; the person that arrived first is the first to go out as well.
17+
A queue is like a line of people at the bank; the person who arrived first is the first to go out.
1818

1919
Similar to the stack, we only have two operations (insert and remove). In a Queue, we add elements to the back of the list and remove it from the front.
2020

21-
We could use an array or a linked list to implement a Queue. However, it is recommended only to use a linked list. Why? An array has a linear runtime _O(n)_ to remove an element from the start while a linked list has constant time _O(1)_.
21+
We could use an array or a linked list to implement a Queue. However, it is recommended only to use a linked list. Why? An array has a linear runtime _O(n)_ to remove an element from the start, while a linked list has constant time _O(1)_.
2222

2323
.Queue's constructor
2424
[source, javascript]
@@ -56,15 +56,15 @@ As discussed, this operation has a constant runtime.
5656

5757
==== Implementation usage
5858

59-
We can use our Queue class like follows:
59+
We can use our Queue class as follows:
6060

6161
.Queue usage example
6262
[source, javascript]
6363
----
6464
include::{codedir}/data-structures/queues/queue.js[tag=snippet, indent=0]
6565
----
6666

67-
You can see that the items are dequeued in the same order they were added, FIFO (first-in, first out).
67+
You can see that the items are dequeued in the same order they were added, FIFO (first-in, first-out).
6868

6969
==== Queue Complexity
7070

@@ -81,3 +81,66 @@ As an experiment, we can see in the following table that if we had implemented t
8181
|===
8282
// end::table[]
8383
indexterm:[Runtime, Linear]
84+
85+
86+
==== Practice Questions
87+
(((Interview Questions, Queue)))
88+
89+
90+
// tag::queue-q-recent-counter[]
91+
===== Recent Counter
92+
93+
*QU-1*) _Design a class that counts the most recent requests within a time window._
94+
95+
Example:
96+
97+
[source, javascript]
98+
----
99+
const counter = new RecentCounter(10); // The time window is 10 ms.
100+
counter.request(1000); // 1 (first request, it always counts)
101+
counter.request(3000); // 1 (last requests was 2000 ms ago, > 10ms, so doesn't count)
102+
counter.request(3100); // 1 (last requests was 100 ms ago, > 10ms, so doesn't count)
103+
counter.request(3105); // 2 (last requests was 5 ms ago, <= 10ms, so it counts)
104+
----
105+
106+
_Seen in interviews at: Google, Bloomberg, Yandex_
107+
// end::queue-q-recent-counter[]
108+
109+
110+
[source, javascript]
111+
----
112+
include::../../interview-questions/recent-counter.js[tag=description]
113+
// write you code here
114+
}
115+
----
116+
117+
_Solution: <<queue-q-recent-counter>>_
118+
119+
120+
// tag::queue-q-design-snake-game[]
121+
===== Design Snake Game
122+
123+
*QU-2*) _Design the `move` function for the snake game. The move function returns an integer representing the current score. If the snake goes out of the given height and width or hit itself the game is over and return `-1`._
124+
125+
Example:
126+
127+
[source, javascript]
128+
----
129+
const snakeGame = new SnakeGame(4, 2, [[1, 2], [0, 1]]);
130+
expect(snakeGame.move('R')).toEqual(0); // 0
131+
expect(snakeGame.move('D')).toEqual(0); // 0
132+
expect(snakeGame.move('R')).toEqual(1); // 1 (ate food1)
133+
expect(snakeGame.move('U')).toEqual(1); // 1
134+
expect(snakeGame.move('L')).toEqual(2); // 2 (ate food2)
135+
expect(snakeGame.move('U')).toEqual(-1); // -1 (hit wall)
136+
----
137+
138+
_Seen in interviews at: Amazon, Bloomberg, Apple_
139+
// end::queue-q-design-snake-game[]
140+
141+
[source, javascript]
142+
----
143+
include::../../interview-questions/design-snake-game.js[tag=description]
144+
----
145+
146+
_Solution: <<queue-q-design-snake-game>>_

‎book/content/part02/stack.asc

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,26 @@ Implementing the stack with an array and linked list would lead to the same time
8787
It's not very common to search for values on a stack (other Data Structures are better suited for this). Stacks are especially useful for implementing <<part03-graph-data-structures#dfs-tree, Depth-First Search>>.
8888

8989

90-
==== Interview Questions
91-
(((Interview Questions, Arrays)))
92-
93-
94-
95-
90+
==== Practice Questions
91+
(((Interview Questions, Stack)))
9692

9793
// tag::stack-q-valid-parentheses[]
9894
===== Validate Parentheses / Braces / Brackets
9995

10096
*ST-1*) _Given an string with 3 types of brakets: `()`, `{}`, and `[]`. Validate they are properly closed and opened._
97+
98+
Examples:
99+
100+
[source, javascript]
101+
----
102+
isParenthesesValid('(){}[]'); // true
103+
isParenthesesValid('('); // false (closing parentheses is missing)
104+
isParenthesesValid('([{}])'); // true
105+
isParenthesesValid('[{]}'); // false (brakets are not closed in the right order)
106+
isParenthesesValid('([{)}]'); // false (closing is out of order)
107+
----
108+
109+
_Seen in interviews at: Amazon, Bloomberg, Facebook, Citadel_
101110
// end::stack-q-valid-parentheses[]
102111

103112
[source, javascript]
@@ -117,6 +126,16 @@ _Solution: <<stack-q-valid-parentheses>>_
117126
===== Daily Temperaturs
118127

119128
*ST-2*) _Given an array of integers from 30 to 100 (daily temperatures), return another array that for each day in the input, tells you how many days you would have to wait until a warmer temperature. If no warmer temperature is possible then return `0` for that element._
129+
130+
Examples:
131+
132+
[source, javascript]
133+
----
134+
dailyTemperatures([30, 28, 50, 40, 30]); // [2 (to 50), 1 (to 28), 0, 0, 0]
135+
dailyTemperatures([73, 69, 72, 76, 73, 100]); // [3, 1, 1, 0, 1, 100]
136+
----
137+
138+
_Seen in interviews at: Amazon, Adobe, Cisco_
120139
// end::stack-q-daily-temperatures[]
121140

122141
[source, javascript]

‎book/interview-questions/daily-temperatures.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* for each elem from the input.
66
*
77
* @examples
8-
* dailyTemperatures([30, 28, 50, 40, 30]); // [2, 1, 0, 0, 0]
9-
* dailyTemperatures([73, 69, 72, 76, 73]); // [3, 1, 1, 0, 0]
8+
* dailyTemperatures([30, 28, 50, 40, 30]); // [2, 1, 0, 0, 0]
9+
* dailyTemperatures([73, 69, 72, 76, 73]); // [3, 1, 1, 0, 0]
1010
*
1111
* @param {number[]} t - Daily temperatures
1212
*/
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const { Queue } = require('../../src/index');
2+
3+
// tag::description[]
4+
/**
5+
* The snake game stars with a snake of length 1 at postion 0,0.
6+
* Only one food position is shown at a time. Once it's eaten the next one shows up.
7+
* The snake can move in four directions up, down, left and right.
8+
* If the snake go out of the boundaries (width x height) the game is over.
9+
* If the snake hit itself the game is over.
10+
* When the game is over, the `move` method returns -1 otherwise, return the current score.
11+
*
12+
* @example
13+
* const snakeGame = new SnakeGame(3, 2, [[1, 2], [0, 1]]);
14+
* snakeGame.move('R'); // 0
15+
* snakeGame.move('D'); // 0
16+
* snakeGame.move('R'); // 0
17+
* snakeGame.move('U'); // 1
18+
* snakeGame.move('L'); // 2
19+
* snakeGame.move('U'); // -1
20+
*/
21+
class SnakeGame {
22+
// end::description[]
23+
// tag::solution[]
24+
25+
// end::solution[]
26+
// tag::description[]
27+
/**
28+
* Initialize game with grid's dimension and food order.
29+
* @param {number} width - The screen width (grid's columns)
30+
* @param {number} height - Screen height (grid's rows)
31+
* @param {number[]} food - Food locations.
32+
*/
33+
constructor(width, height, food) {
34+
// end::description[]
35+
// tag::solution[]
36+
this.width = width;
37+
this.height = height;
38+
this.food = new Queue(food);
39+
this.snake = new Queue([[0, 0]]);
40+
this.tail = new Set([[0, 0]]);
41+
this.dirs = {
42+
U: [-1, 0], D: [1, 0], R: [0, 1], L: [0, -1],
43+
};
44+
// end::solution[]
45+
// tag::description[]
46+
}
47+
// end::description[]
48+
49+
// tag::description[]
50+
/**
51+
* Move snake 1 position into the given direction.
52+
* It returns the score or game over (-1) if the snake go out of bound or hit itself.
53+
* @param {string} direction - 'U' = Up, 'L' = Left, 'R' = Right, 'D' = Down.
54+
* @returns {number} - The current score (snake.length - 1).
55+
*/
56+
move(direction) {
57+
// end::description[]
58+
// tag::solution[]
59+
let [r, c] = this.snake.back(); // head of the snake
60+
[r, c] = [r + this.dirs[direction][0], c + this.dirs[direction][1]];
61+
62+
// check wall collision
63+
if (r < 0 || c < 0 || r >= this.height || c >= this.width) return -1;
64+
65+
const [fr, fc] = this.food.front() || []; // peek
66+
if (r === fr && c === fc) {
67+
this.food.dequeue(); // remove eaten food.
68+
} else {
69+
this.snake.dequeue(); // remove snake's if not food was eaten
70+
this.tail.delete(this.tail.keys().next().value);
71+
}
72+
73+
// check collision with snake's tail
74+
if (this.tail.has(`${r},${c}`)) return -1; // O(1)
75+
76+
this.snake.enqueue([r, c]); // add new position
77+
this.tail.add(`${r},${c}`);
78+
79+
return this.snake.size - 1; // return score (length of the snake - 1)
80+
// end::solution[]
81+
// tag::description[]
82+
}
83+
}
84+
// end::description[]
85+
86+
module.exports = { SnakeGame };
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const { SnakeGame } = require('./design-snake-game');
2+
3+
describe('Queue: Design Snake Game', () => {
4+
it('should game over when hits wall', () => {
5+
const snakeGame = new SnakeGame(4, 2, [[1, 2], [0, 1]]);
6+
expect(snakeGame.move('R')).toEqual(0); // 0
7+
expect(snakeGame.move('D')).toEqual(0); // 0
8+
expect(snakeGame.move('R')).toEqual(1); // 1 (ate food1)
9+
expect(snakeGame.move('U')).toEqual(1); // 1
10+
expect(snakeGame.move('L')).toEqual(2); // 2 (ate food2)
11+
expect(snakeGame.move('U')).toEqual(-1); // -1 (hit wall)
12+
});
13+
14+
it('should circle around without eating itself', () => {
15+
const snakeGame = new SnakeGame(2, 2, [[0, 1], [1, 1], [1, 0]]);
16+
expect(snakeGame.move('R')).toEqual(1);
17+
expect(snakeGame.move('D')).toEqual(2);
18+
expect(snakeGame.move('L')).toEqual(3);
19+
expect(snakeGame.move('U')).toEqual(3);
20+
expect(snakeGame.move('R')).toEqual(3);
21+
});
22+
23+
it('should game over when hit itself', () => {
24+
const snakeGame = new SnakeGame(3, 2, [[0, 1], [0, 2], [1, 2], [1, 1]]);
25+
expect(snakeGame.move('R')).toEqual(1);
26+
expect(snakeGame.move('R')).toEqual(2);
27+
expect(snakeGame.move('D')).toEqual(3);
28+
expect(snakeGame.move('L')).toEqual(4);
29+
expect(snakeGame.move('U')).toEqual(-1);
30+
});
31+
});

‎book/interview-questions/max-subarray.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// tag::description[]
22
/**
33
* Find the maximun sum of contiguous elements in an array.
4+
*
45
* @examples
5-
* maxSubArray([1, -3, 10, -5]); // => 10
6-
* maxSubArray([-3,4,-1,2,1,-5]); // => 6
6+
* maxSubArray([1, -3, 10, -5]); // => 10
7+
* maxSubArray([-3,4,-1,2,1,-5]); // => 6
78
*
89
* @param {number[]} a - Array
910
*/

‎book/interview-questions/max-subarray.spec.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ describe('Max Subarray Sum', () => {
88
expect(fn([-2, 1, -3, 4, -1, 2, 1, -5, 4])).toEqual(6);
99
});
1010

11+
it('should work with small arrays', () => {
12+
expect(fn([1, -3, 10, -5])).toEqual(10);
13+
});
14+
1115
it('should work with large arrays', () => {
1216
expect(fn(largeArray)).toEqual(4853);
1317
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const { Queue } = require('../../src/index');
2+
3+
// tag::description[]
4+
/**
5+
* Counts the most recent requests within a time window.
6+
* Each request has its timestamp.
7+
* If the time window is 2 seconds,
8+
* any requests that happened more than 2 seconds before the most recent request
9+
* should not count.
10+
*
11+
* @example - The time window is 3 sec. (3000 ms)
12+
* const counter = new RecentCounter(3000);
13+
* counter.request(100); // 1
14+
* counter.request(1000); // 2
15+
* counter.request(3000); // 3
16+
* counter.request(3100); // 4
17+
* counter.request(3101); // 4
18+
*
19+
*/
20+
class RecentCounter {
21+
// end::description[]
22+
// tag::solution[]
23+
queue = new Queue();
24+
// end::solution[]
25+
// tag::description[]
26+
/**
27+
* @param {number} maxWindow - Max. time window (in ms) for counting requests
28+
* Defaults to 1 second (1000 ms)
29+
*/
30+
constructor(maxWindow = 1000) {
31+
// end::description[]
32+
// tag::solution[]
33+
this.window = maxWindow;
34+
// end::solution[]
35+
// tag::description[]
36+
}
37+
38+
/**
39+
* Add new request and calculate the current count within the window.
40+
* @param {number} timestamp - The current timestamp (increasing order)
41+
* @return {number} - The number of requests within the time window.
42+
*/
43+
request(timestamp) {
44+
// end::description[]
45+
// tag::solution[]
46+
this.queue.enqueue(timestamp);
47+
while (timestamp - this.queue.peek() > this.window)
48+
this.queue.dequeue();
49+
50+
return this.queue.size;
51+
// end::solution[]
52+
// tag::description[]
53+
}
54+
}
55+
// end::description[]
56+
57+
module.exports = { RecentCounter };
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const { RecentCounter } = require('./recent-counter');
2+
3+
describe('Queue: Recent Counter', () => {
4+
it('should count requests within the window', () => {
5+
const counter = new RecentCounter(3000);
6+
expect(counter.request(100)).toEqual(1); // 1
7+
expect(counter.request(1000)).toEqual(2); // 2
8+
expect(counter.request(3000)).toEqual(3); // 3
9+
expect(counter.request(3100)).toEqual(4); // 4
10+
expect(counter.request(3101)).toEqual(4); // 4 (request at time 100 is out of the 3000 window).
11+
});
12+
13+
it('should NOT count requests out of the window', () => {
14+
const counter = new RecentCounter(10);
15+
expect(counter.request(100)).toEqual(1);
16+
expect(counter.request(1000)).toEqual(1);
17+
expect(counter.request(3000)).toEqual(1);
18+
expect(counter.request(3100)).toEqual(1);
19+
expect(counter.request(3101)).toEqual(2);
20+
});
21+
});

‎src/data-structures/queues/queue.js

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
const LinkedList = require('../linked-lists/linked-list');
22

3+
/* Usage Example:
4+
// tag::snippet[]
5+
const queue = new Queue();
6+
7+
queue.enqueue('a');
8+
queue.enqueue('b');
9+
queue.dequeue(); //↪️ a
10+
queue.enqueue('c');
11+
queue.dequeue(); //↪️ b
12+
queue.dequeue(); //↪️ c
13+
// end::snippet[]
14+
// */
15+
316
// tag::constructor[]
417
/**
518
* Data structure where we add and remove elements in a first-in, first-out (FIFO) fashion
619
*/
720
class Queue {
8-
constructor() {
9-
this.items = new LinkedList();
21+
constructor(iterable = []) {
22+
this.items = new LinkedList(iterable);
1023
}
1124
// end::constructor[]
1225

1326
// tag::enqueue[]
1427
/**
15-
* Add element to the queue
28+
* Add element to the back of the queue.
1629
* Runtime: O(1)
1730
* @param {any} item
1831
* @returns {queue} instance to allow chaining.
@@ -25,7 +38,7 @@ class Queue {
2538

2639
// tag::dequeue[]
2740
/**
28-
* Remove element from the queue
41+
* Remove element from the front of the queue.
2942
* Runtime: O(1)
3043
* @returns {any} removed value.
3144
*/
@@ -47,23 +60,30 @@ class Queue {
4760
isEmpty() {
4861
return !this.items.size;
4962
}
63+
64+
/**
65+
* Return the most recent value or null if empty.
66+
*/
67+
back() {
68+
if (this.isEmpty()) return null;
69+
return this.items.last.value;
70+
}
71+
72+
/**
73+
* Return oldest value from the queue or null if empty.
74+
* (Peek at the next value to be dequeue)
75+
*/
76+
front() {
77+
if (this.isEmpty()) return null;
78+
return this.items.first.value;
79+
}
5080
}
5181

5282
// Aliases
83+
Queue.prototype.peek = Queue.prototype.front;
5384
Queue.prototype.add = Queue.prototype.enqueue;
85+
Queue.prototype.push = Queue.prototype.enqueue;
5486
Queue.prototype.remove = Queue.prototype.dequeue;
87+
Queue.prototype.pop = Queue.prototype.dequeue;
5588

5689
module.exports = Queue;
57-
58-
/* Usage Example:
59-
// tag::snippet[]
60-
const queue = new Queue();
61-
62-
queue.enqueue('a');
63-
queue.enqueue('b');
64-
queue.dequeue(); //↪️ a
65-
queue.enqueue('c');
66-
queue.dequeue(); //↪️ b
67-
queue.dequeue(); //↪️ c
68-
// end::snippet[]
69-
// */

‎src/data-structures/queues/queue.spec.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,28 @@ describe('Queue', () => {
4646
expect(queue.isEmpty()).toBe(false);
4747
});
4848
});
49+
50+
describe('#back', () => {
51+
it('should return null if empty', () => {
52+
expect(queue.back()).toEqual(null);
53+
});
54+
55+
it('should return newest element', () => {
56+
queue.enqueue('oldest');
57+
queue.enqueue('newest');
58+
expect(queue.back()).toEqual('newest');
59+
});
60+
});
61+
62+
describe('#front', () => {
63+
it('should return null if empty', () => {
64+
expect(queue.front()).toEqual(null);
65+
});
66+
67+
it('should return oldest element', () => {
68+
queue.enqueue('oldest');
69+
queue.enqueue('newest');
70+
expect(queue.front()).toEqual('oldest');
71+
});
72+
});
4973
});

0 commit comments

Comments
 (0)
Please sign in to comment.