From 9a887660bd21e7dbda0d3e87af704b4e41874093 Mon Sep 17 00:00:00 2001 From: Adrian Mejia Date: Fri, 28 Aug 2020 15:41:16 -0400 Subject: [PATCH] feat(book/queue): add solution for queue question --- book/D-interview-questions-solutions.asc | 70 +++++++++++++++ book/content/part02/linked-list.asc | 5 +- book/content/part02/queue.asc | 38 ++++++++ book/content/part02/stack.asc | 6 +- book/interview-questions/design-snake-game.js | 86 +++++++++++++++++++ .../design-snake-game.spec.js | 31 +++++++ 6 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 book/interview-questions/design-snake-game.js create mode 100644 book/interview-questions/design-snake-game.spec.js diff --git a/book/D-interview-questions-solutions.asc b/book/D-interview-questions-solutions.asc index 5fe1acf1..13a7ee85 100644 --- a/book/D-interview-questions-solutions.asc +++ b/book/D-interview-questions-solutions.asc @@ -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] diff --git a/book/content/part02/linked-list.asc b/book/content/part02/linked-list.asc index 7790c3e4..71b68c82 100644 --- a/book/content/part02/linked-list.asc +++ b/book/content/part02/linked-list.asc @@ -287,10 +287,7 @@ Use a doubly linked list when: For the next two linear data structures <> and <>, 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 diff --git a/book/content/part02/queue.asc b/book/content/part02/queue.asc index aab404a7..46793934 100644 --- a/book/content/part02/queue.asc +++ b/book/content/part02/queue.asc @@ -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: <>_ + + + + + +// // 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: <>_ diff --git a/book/content/part02/stack.asc b/book/content/part02/stack.asc index 02643814..ae00254b 100644 --- a/book/content/part02/stack.asc +++ b/book/content/part02/stack.asc @@ -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 diff --git a/book/interview-questions/design-snake-game.js b/book/interview-questions/design-snake-game.js new file mode 100644 index 00000000..6d9d7cf3 --- /dev/null +++ b/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 }; diff --git a/book/interview-questions/design-snake-game.spec.js b/book/interview-questions/design-snake-game.spec.js new file mode 100644 index 00000000..acfcbd1f --- /dev/null +++ b/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); + }); +});