Skip to content

Commit

Permalink
feat(book/hashmap): add exercises and solutions
Browse files Browse the repository at this point in the history
  • Loading branch information
amejiarosario committed Sep 3, 2020
1 parent 1bfe522 commit d18186b
Show file tree
Hide file tree
Showing 8 changed files with 421 additions and 33 deletions.
171 changes: 163 additions & 8 deletions book/D-interview-questions-solutions.asc
Expand Up @@ -196,11 +196,11 @@ If the index overflows, it moves to the next node and reset the index to zero.
[#stack-q-valid-parentheses]
include::content/part02/stack.asc[tag=stack-q-valid-parentheses]

.We need to validate that brackets are properly opened and closed, following these rules:
.We need to validate that brackets are correctly opened and closed, following these rules:
- An opened bracket must be close by the same type.
- Open brackets mush be closed in the correct order.

This is a parsing problem, and usually, stacks are good candidates for them.
We are facing a parsing problem, and usually, stacks are good candidates for them.

*Algorithm*:

Expand Down Expand Up @@ -391,8 +391,8 @@ We can visit the tree using a Queue and keep track when a level ends, and the ne

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.

.There are several ways to solve this problem using BFS. Here are some ideas:
- *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, '*']);`.
.There are several ways to solve this problem by using BFS. Here are some ideas:
- *1 Queue + Sentinel node*: we can use a special character in the `Queue` like `'*'` or `null` to indicate a level change. So, we would start something like this `const queue = new Queue([root, '*']);`.
- *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.
- *1 Queue + size tracking*: we track the Queue's `size` before the children are enqueued. That way, we know where the current level ends.

Expand Down Expand Up @@ -428,8 +428,164 @@ The complexity of any of the BFS methods or DFS is similar.
- 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`.


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

:leveloffset: +1

=== Solutions for Hash Map Questions
(((Interview Questions Solutions, Hash Map)))

:leveloffset: -1

[#hashmap-q-two-sum]
include::content/part03/hashmap.asc[tag=hashmap-q-two-sum]

This simple problem can have many solutions; let's explore some.

_Brute force_

One brute force approach could be doing two for loops. We sum two different numbers and check if they add up to the target. If yes, we return, and if not, we keep increasing the indices until we check every possible pair.

[source, javascript]
----
include::interview-questions/two-sum.js[tags=twoSumBrute]
----

This approach's time complexity is `O(n^2)`, because we visit every number twice in the worst-case. While the space complexity is `O(1)`.

Can we trade space for time? Yes!

_Map_

Based on `nums[i] + nums[j] === target` we can say that `num[j] === target - nums[i]`. We can do one pass and check if we have seen any number equal to `target - nums[i]`. A map is perfect for this job. We could have a HashMap that maps `num` to `index`. Let's see the algorithms to make it work.


*Algorithm*:

* Visit every number once
** Calculate the complement `target - nums[i]`.
** If the complement exists, return its index and the current index.
** If not, save the complement and the index number.

*Implementation*:

[source, javascript]
----
include::interview-questions/two-sum.js[tags=description;solution]
----

*Complexity Analysis*:

- Time: `O(n)`. We visit every number once.
- Space: `O(n)`. In the worst-case scenario, we don't find the target, and we ended up with a map with all the numbers from the array.


[#hashmap-q-subarray-sum-equals-k]
include::content/part03/hashmap.asc[tag=hashmap-q-subarray-sum-equals-k]

This problem has multiple ways to solve it. Let's explore some.

_Brute force_

The most straightforward one is to convert the requirements into code:
generate all possible subarrays, add them up, and check how many are equal to k.

[source, javascript]
----
include::interview-questions/subarray-sum-equals-k.js[tags=subarraySumBrute1]
----

This solution's time complexity is `O(n^3)` because of the 3 nested loops.

How can we do better? Notice that the last for loop, compute the sum repeatedly just to add one more.
Let's fix that!

_Cummulative Sum_

For this solution, instead of computing the sum from `i` to `j` all the time. We can calculate a cumulative sum. Every time we see a new number, we add it to the aggregate.

Since we want all possible subarray, We can increase `i` and get sum for each:

[source, javascript]
----
array = [1, 2, 3, 0, 1, 4, 0, 5];
// cummulative sum from left to right with i = 0
sum = [1, 3, 6, 6, 7, 11, 11, 16];
// cummulative sum from left to right with i = 1
sum = [2, 5, 5, 6, 10, 10, 15];
// cummulative sum from left to right with i = 2
sum = [3, 3, 4, 8, 8, 13];
// ... and so on ...
// cummulative sum from left to right with i = 7
sum = [5];
----

Here's the code:

[source, javascript]
----
include::interview-questions/subarray-sum-equals-k.js[tags=subarraySumBrute1]
----

The time complexity for this solution is better, `O(n^2)`. Can we still do better?

_Map_

Let's get the intution from our previous cummulative sum:

[source, javascript]
----
subarraySum([1, 2, 3, 0, 1, 4, 0, 5], 5); // k = 5
// cummulative sum from left to right is
sum = [1, 3, 6, 6, 7, 11, 11, 16];
// ^ ^
----

Notice that when the array has a 0, the cumulative sum has a repeated number. If you subtract those numbers, it will give you zero. In the same way, If you take two other ranges and subtract them (`sum[j] - sum[i]`), it will give you the sum of that range `sum(num[i]...num[j])`.

For instance, if we take the index `2` and `0` (with values 6 and 1) and susbtract them we get `6-1=5`. To verify we can add the array values from index 0 to 2, `sum([1, 2, 3]) === 5`.

With that intuition, we can use a Map to keep track of the aggregated sum and the number of times that sum.

*Algorithm*:

* Start sum at 0
* Visit every number on the array
** Compute the cumulative sum
** Check if `sum - k` exits; if so, it means that there's a subarray that adds up to k.
** Save the sum and the number of times that it has occurred.

*Implementation*:

[source, javascript]
----
include::interview-questions/subarray-sum-equals-k.js[tags=description;solution]
----

You might wonder, what the map is initialized with `[0, 1]`. Consider this test case:

[source, javascript]
----
subarraySum([1], 1); // k = 1
----

The sum is 1, however `sum - k` is `0`. If it doesn't exist on the map, we will get the wrong answer since that number adds up to `k`. We need to add an initial case on the map: `map.set(0, 1)`. If `nums[i] - k = 0`, then that means that `nums[i] = k` and should be part of the solution.

*Complexity Analysis*:

- Time: `O(n)`. We visit every number once.
- Space: `O(n)`. The map size will be the same as the original array.

// :leveloffset: +1

// === Solutions for TOPIC Questions
// (((Interview Questions Solutions, TOPIC)))

// :leveloffset: -1

// [#TOPIC-q-FILENAME]
// include::content/part03/TOPIC_FILE.asc[tag=TOPIC-q-FILENAME]

// RESTATE REQUIREMENTS AND DESCRIPTIONS

Expand All @@ -444,8 +600,7 @@ The complexity of any of the BFS methods or DFS is similar.

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

// IMPLEMENTATION NOTES
Expand Down

0 comments on commit d18186b

Please sign in to comment.