Skip to content

Commit

Permalink
feat(book/queue): add solution for queue question
Browse files Browse the repository at this point in the history
  • Loading branch information
amejiarosario committed Aug 28, 2020
1 parent 04aa9db commit 9a88766
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 9 deletions.
70 changes: 70 additions & 0 deletions book/D-interview-questions-solutions.asc
Expand Up @@ -267,6 +267,76 @@ The stack contains the indexes rather than the temperatures themselves.



:leveloffset: +1

=== Solutions for Queue Questions
(((Interview Questions Solutions, Queue)))

:leveloffset: -1


[#queue-q-design-snake-game]
include::content/part02/queue.asc[tag=queue-q-design-snake-game]

This game is perfect to practice working with Queues. There are at least two opportunities to use a Queue. One for the food location and another for the snake body part movements. One very move, we insert a new position into the snake and dequeue the last position to indicate the snake moved. Everytime the snake eats food, it grows one more unit, the food gets dequeue and we place the location of next food (if any).

*Algorithm*:

- Based on the snake head current position, calculate the next position based on the given move `direction`.
- If the new position is outside the boundaries, game over(return -1)
- If the new position has food, remove that eaten food from its queue and go to the next.
- If the new position doesn't have food, remove the tail of the snake since it moved.
- If the snake new position hits itself, game over (return -1). To make this check we have to options:
- Queue: we can visit all the elements on snake queue (body) and check if new position collide. That's `O(n)`
- Set: we can maintain a `set` that has all the snake locations so the check is `O(1)`.
- Move the snake head to new location (enqueue)
- Return the score (snake's length - 1);

*Implementation*:

[source, javascript]
----
include::interview-questions/design-snake-game.js[tag=description]
include::interview-questions/design-snake-game.js[tag=solution]
----

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

*Complexity Analysis*:

- Time: `O(1)`. Insert/Remove from Queue is constant time. Check body collisions is `O(1)` as well when using a set. If you traversed the snake queue, it would be `O(n)`, where `n` is the max length of the snake which is the size of the screen (height x width).
- Space: `O(n + m)`. `m` is the number of food items and `n` is the maximun size of the snake which is (height x width).


[#queue-q-FILENAME]
include::content/part02/queue.asc[tag=queue-q-FILENAME]

RESTATE REQUIREMENTS AND DESCRIPTIONS

*Algorithm*:

- STEP 1
- STEP 2
- STEP 2.1
- STEP 2.2

*Implementation*:

[source, javascript]
----
include::interview-questions/FILENAME.js[tag=description]
include::interview-questions/FILENAME.js[tag=solution]
----

IMPLEMENTATION NOTES

*Complexity Analysis*:

- Time: `O(?)`. WHY?
- Space: `O(?)`. WHY?



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

Expand Down
5 changes: 1 addition & 4 deletions book/content/part02/linked-list.asc
Expand Up @@ -287,10 +287,7 @@ Use a doubly linked list when:
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.

==== Interview Questions
(((Interview Questions, Arrays)))



(((Interview Questions, Linked Lists)))

// tag::linkedlist-q-merge-lists[]
===== Merge Linked Lists into One
Expand Down
38 changes: 38 additions & 0 deletions book/content/part02/queue.asc
Expand Up @@ -81,3 +81,41 @@ As an experiment, we can see in the following table that if we had implemented t
|===
// end::table[]
indexterm:[Runtime, Linear]


==== Interview Questions
(((Interview Questions, Queue)))

// tag::queue-q-design-snake-game[]
===== Design Snake Game

*QU-1*) _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 return `-1` for game over._
// end::queue-q-design-snake-game[]

_Question seen at: Amazon, Bloomberg, Apple_

[source, javascript]
----
include::../../interview-questions/design-snake-game.js[tag=description]
----

_Solution: <<queue-q-design-snake-game>>_





// // tag::queue-q-name-2[]
// ===== NAME2

// *QU-2*) _DESCRIPTION_
// // end::queue-q-name-2[]

// [source, javascript]
// ----
// include::../../interview-questions/name-2.js[tag=description]
// // write you code here
// }
// ----

// _Solution: <<queue-q-name-2>>_
6 changes: 1 addition & 5 deletions book/content/part02/stack.asc
Expand Up @@ -88,11 +88,7 @@ It's not very common to search for values on a stack (other Data Structures are


==== Interview Questions
(((Interview Questions, Arrays)))




(((Interview Questions, Stack)))

// tag::stack-q-valid-parentheses[]
===== Validate Parentheses / Braces / Brackets
Expand Down
86 changes: 86 additions & 0 deletions book/interview-questions/design-snake-game.js
@@ -0,0 +1,86 @@
const { Queue } = require('../../src/index');

// tag::description[]
/**
* The snake game stars with a snake of length 1 at postion 0,0.
* Only one food position is shown at a time. Once it's eaten the next one shows up.
* The snake can move in four directions up, down, left and right.
* If the snake go out of the boundaries (width x height) the game is over.
* If the snake hit itself the game is over.
* When the game is over, the `move` method returns -1 otherwise, return the current score.
*
* @example
* const snakeGame = new SnakeGame(3, 2, [[1, 2], [0, 1]]);
* snakeGame.move('R'); // 0
* snakeGame.move('D'); // 0
* snakeGame.move('R'); // 0
* snakeGame.move('U'); // 1 (ate the food1)
* snakeGame.move('L'); // 2 (ate the food2)
* snakeGame.move('U'); // -1 (hit the upper wall)
*/
class SnakeGame {
// end::description[]
// tag::solution[]

// end::solution[]
// tag::description[]
/**
* Initialize game with grid's dimension and food order.
* @param {number} width - The screen width (grid's columns)
* @param {number} height - Screen height (grid's rows)
* @param {number[]} food - Food locations.
*/
constructor(width, height, food) {
// end::description[]
// tag::solution[]
this.width = width;
this.height = height;
this.food = new Queue(food);
this.snake = new Queue([[0, 0]]);
this.tail = new Set([[0, 0]]);
this.dirs = {
U: [-1, 0], D: [1, 0], R: [0, 1], L: [0, -1],
};
// end::solution[]
// tag::description[]
}
// end::description[]

// tag::description[]
/**
* Move snake 1 position into the given direction.
* It returns the score or game over (-1) if the snake go out of bound or hit itself.
* @param {string} direction - 'U' = Up, 'L' = Left, 'R' = Right, 'D' = Down.
* @returns {number} - The current score (snake.length - 1).
*/
move(direction) {
// end::description[]
// tag::solution[]
let [r, c] = this.snake.back(); // head of the snake
[r, c] = [r + this.dirs[direction][0], c + this.dirs[direction][1]];

// check wall collision
if (r < 0 || c < 0 || r >= this.height || c >= this.width) return -1;

const [fr, fc] = this.food.front() || []; // peek
if (r === fr && c === fc) {
this.food.dequeue(); // remove eaten food.
} else {
this.snake.dequeue(); // remove snake's if not food was eaten
this.tail.delete(this.tail.keys().next().value);
}

// check collision with snake's tail
if (this.tail.has(`${r},${c}`)) return -1; // O(1)

this.snake.enqueue([r, c]); // add new position
this.tail.add(`${r},${c}`);

return this.snake.size - 1; // return score (length of the snake - 1)
// end::solution[]
// tag::description[]
}
}
// end::description[]

module.exports = { SnakeGame };
31 changes: 31 additions & 0 deletions book/interview-questions/design-snake-game.spec.js
@@ -0,0 +1,31 @@
const { SnakeGame } = require('./design-snake-game');

describe('Queue: Design Snake Game', () => {
it('should game over when hits wall', () => {
const snakeGame = new SnakeGame(4, 2, [[1, 2], [0, 1]]);
expect(snakeGame.move('R')).toEqual(0); // 0
expect(snakeGame.move('D')).toEqual(0); // 0
expect(snakeGame.move('R')).toEqual(1); // 1 (ate food1)
expect(snakeGame.move('U')).toEqual(1); // 1
expect(snakeGame.move('L')).toEqual(2); // 2 (ate food2)
expect(snakeGame.move('U')).toEqual(-1); // -1 (hit wall)
});

it('should circle around without eating itself', () => {
const snakeGame = new SnakeGame(2, 2, [[0, 1], [1, 1], [1, 0]]);
expect(snakeGame.move('R')).toEqual(1);
expect(snakeGame.move('D')).toEqual(2);
expect(snakeGame.move('L')).toEqual(3);
expect(snakeGame.move('U')).toEqual(3);
expect(snakeGame.move('R')).toEqual(3);
});

it('should game over when hit itself', () => {
const snakeGame = new SnakeGame(3, 2, [[0, 1], [0, 2], [1, 2], [1, 1]]);
expect(snakeGame.move('R')).toEqual(1);
expect(snakeGame.move('R')).toEqual(2);
expect(snakeGame.move('D')).toEqual(3);
expect(snakeGame.move('L')).toEqual(4);
expect(snakeGame.move('U')).toEqual(-1);
});
});

0 comments on commit 9a88766

Please sign in to comment.