From d18186b61c260e3ae2cc1267cadd16c8cb453e00 Mon Sep 17 00:00:00 2001 From: Adrian Mejia Date: Thu, 3 Sep 2020 14:53:55 -0400 Subject: [PATCH] feat(book/hashmap): add exercises and solutions --- book/D-interview-questions-solutions.asc | 171 +++++++++++++++++- book/content/part03/hashmap.asc | 120 +++++++++--- ...-substring-without-repeating-characters.js | 14 ++ ...tring-without-repeating-characters.spec.js | 5 + .../subarray-sum-equals-k.js | 58 ++++++ .../subarray-sum-equals-k.spec.js | 24 +++ book/interview-questions/two-sum.js | 34 ++++ book/interview-questions/two-sum.spec.js | 28 +++ 8 files changed, 421 insertions(+), 33 deletions(-) create mode 100644 book/interview-questions/longest-substring-without-repeating-characters.js create mode 100644 book/interview-questions/longest-substring-without-repeating-characters.spec.js create mode 100644 book/interview-questions/subarray-sum-equals-k.js create mode 100644 book/interview-questions/subarray-sum-equals-k.spec.js create mode 100644 book/interview-questions/two-sum.js create mode 100644 book/interview-questions/two-sum.spec.js diff --git a/book/D-interview-questions-solutions.asc b/book/D-interview-questions-solutions.asc index 6adf64c0..71e65e48 100644 --- a/book/D-interview-questions-solutions.asc +++ b/book/D-interview-questions-solutions.asc @@ -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*: @@ -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. @@ -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 @@ -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 diff --git a/book/content/part03/hashmap.asc b/book/content/part03/hashmap.asc index 8bf36ede..ec8d8ea4 100644 --- a/book/content/part03/hashmap.asc +++ b/book/content/part03/hashmap.asc @@ -12,7 +12,7 @@ A HashMap is a Map implementation. HashMaps are composed of two things: 1) a _hash function_ and 2) a bucket _array_ to store values. -Before going into the implementation details let’s give an overview of how it works. Let’s say we want to keep a tally of things and animals: +Before going into the implementation details, let’s give an overview of how it works. Let’s say we want to keep a tally of things and animals: .HashMap example [source, javascript] @@ -31,11 +31,11 @@ image::image41.png[image,width=528,height=299] 1. We use a *hash function* to transform the keys (e.g., dog, cat, rat, …) into an array index. This _array_ is called *bucket*. 2. The bucket holds the values or list of values in case of collisions. -In the illustration, we have a bucket size of 10. In bucket 0, we have a collision. Both `cat` and `art` keys map to the same bucket even thought their hash codes are different. +In the illustration, we have a bucket size of 10. In bucket 0, we have a collision. Both `cat` and `art` keys map to the same bucket even though their hash codes are different. In a HashMap, a *collision* is when different keys lead to the same index. They are nasty for performance since it can reduce the search time from *O(1)* to *O(n)*. -Having a big bucket size can avoid a collision but also can waste too much memory. We are going to build an _optimized_ HashMap that re-sizes itself when it is getting full. This auto-resizing avoids collisions and don't need to allocate too much memory upfront. Let’s start with the *hash function*. +Having a big bucket size can avoid a collision but also can waste too much memory. We are going to build an _optimized_ HashMap that resizes itself when it is getting full. This auto-resizing avoids collisions and don't need to allocate too much memory upfront. Let’s start with the *hash function*. ===== Designing an optimized hash function @@ -43,7 +43,7 @@ To minimize collisions, we need to create an excellent hash function. IMPORTANT: A *perfect* hash function is one that assigns a unique array index for every different key. -It’s no practical and memory-wise wasteful to have a perfect hash function, so we are going to shoot for a cost-effective hash function instead. +It’s no practical and memory-wise wasteful to have a perfect hash function, so we will shoot for a cost-effective hash function instead. .To recap: - A hash function converts keys into array indices. @@ -51,11 +51,11 @@ It’s no practical and memory-wise wasteful to have a perfect hash function, so 1. *Hash Code*: maps any key into an integer (unbonded) 2. *Compression function*: maps an arbitrary integer to integer in the range of [0… BUCKET_SIZE -1]. -Before doing a great hash function, let's see what a lousy hash function looks like. 😉 +Before doing an excellent hash function, let's see what a lousy hash function looks like. 😉 ====== Analysing collisions on bad hash code functions -The goal of a hash code function is to convert any value given into a positive integer — a common way to accomplish with summing each string’s Unicode value. +A hash code function's goal is to convert any value given into a positive integer — a common way to accomplish with summing each string’s Unicode value. .Naïve hashing function implementation [source, javascript] @@ -72,7 +72,7 @@ The `charCodeAt()` method returns an integer between `0` and `65535` representin The `codePointAt()` method returns a non-negative integer that is the Unicode code point value. **** -With this function we have the can convert some keys to numbers as follows: +With this function, we have the can convert some keys to numbers as follows: .Hashing examples [source, javascript] @@ -82,7 +82,7 @@ include::{codedir}/data-structures/maps/hash-maps/hashing.js[tag=naiveHashCodeEx Notice that `rat` and `art` have the same hash code! These are collisions that we need to solve. -Collisions happened because we are adding the letter's Unicode and are not taking the order into account nor the type. We can do better by offsetting the character value based on their position in the string. We can also add the object type, so number `10` produce different output than string `'10'`. +Collisions happened because we add the letter's Unicode and are not taking the order into account or the type. We can do better by offsetting the character value based on their position in the string. We can also add the object type, so number `10` produce different output than the string `'10'`. .Hashing function implementation that offset character value based on the position [source, javascript] @@ -109,7 +109,7 @@ BigInt has no virtual limits (until you run out of physical memory). It uses the ---- **** -As you can imagine, summing 20bits per letter leads to a humongous number! That's the case even for three letters words. We are using `BigInt`, so it doesn’t overflow. +As you can imagine, summing 20bits per letter leads to a massive number! That's the case even for three-letter words. We are using `BigInt`, so it doesn’t overflow. .Verifying there's not hashing code duplicates [source, javascript] @@ -117,9 +117,9 @@ As you can imagine, summing 20bits per letter leads to a humongous number! That' include::{codedir}/data-structures/maps/hash-maps/hashing.js[tag=hashCodeOffsetExample, indent=0] ---- -We don’t have duplicates anymore! If the keys have different content or type, they have a different hash code. However, we need to represent these unbounded integers to finite buckets in an array. We do that using *compression function*. This function can be as simple as `% BUCKET_SIZE`. +We don’t have duplicates anymore! If the keys have different content or type, they have distinct hash codes. However, we need to represent these unbounded integers to finite buckets in an array. We do that using *compression function*. This function can be as simple as `% BUCKET_SIZE`. -However, there’s an issue with the last implementation. It doesn’t matter how enormous (and different) is the hash code number if we at the end use the modulus to get an array index. The part of the hash code that truly matters is the last bits. +However, there’s an issue with the last implementation. It doesn’t matter how enormous (and different) is the hash code number if we, in the end, use the modulus to get an array index. The part of the hash code that truly matters is the last bits. .Look at this example with a bucket size of 4. [source, javascript] @@ -131,9 +131,9 @@ However, there’s an issue with the last implementation. It doesn’t matter ho 50 % 4 //↪️ 2 ---- -All the hash codes are different and still we get many collisions! [big]#😱# +All the hash codes are different, and still, we get many collisions! [big]#😱# -Based on numbers properties, using a prime number as the modulus produce fewer collisions. +Based on numbers properties, using a prime number as the modulus produces fewer collisions. .Let’s see what happens if the bucket size is a prime number: [source, javascript] @@ -149,7 +149,7 @@ Now it’s more evenly distributed!! [big]#😎👍# .So, to sum up: * Bucket size should always be a *prime number*, so data is distributed more evenly and minimized collisions. -* Hash code doesn’t have to be too big. At the end what matters is the few last digits. +* Hash code doesn’t have to be too big. In the end, what matters is the few last digits. Let’s design a better HashMap with what we learned. @@ -171,9 +171,9 @@ Take a look at the following function: include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=hashFunction, indent=0] ---- -Is somewhat similar to what we did before, in the sense that we use each letter’s Unicode is used to compute the hash. The difference is: +It is somewhat similar to what we did before, in the sense that we use each letter’s Unicode to compute the hash. The difference is: -1. We are using the XOR bitwise operation (`^`) to produce an *avalanche effect*, where a small change in two strings produces completely different hash codes. E.g. +1. We are using the XOR bitwise operation (`^`) to produce an *avalanche effect*, where a small change in two strings makes completely different hash codes. E.g. .Hash Code example using FVN1a [source, javascript] @@ -182,9 +182,9 @@ hashCode('cat') //↪️ 4201630708 hashCode('cats') //↪️ 3304940933 ---- -A one letter change produce a very different output. +A one-letter change produces a very different output. -We are using the FVN-1a prime number (`16777619`) and the offset (`2166136261`) to reduce collisions even further. If you are curious where these numbers come from check out this http://bit.ly/fvn-1a[link]. +We are using the FVN-1a prime number (`16777619`) and the offset (`2166136261`) to reduce collisions even further if you are curious where these numbers come from, check out this http://bit.ly/fvn-1a[link]. FVN-1a hash function is a good trade-off between speed and collision prevention. @@ -208,7 +208,7 @@ include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=getLoadFactor, } ---- -Notice that we are also keeping track of collisions (for benchmarking purposes) and a load factor. *The load factor* measures how full the hash map is. We don’t want to be fuller than 75%. If the HashMap is getting too full, then we are going to fix it doing a *rehash* (more on that later). +Notice that we are also keeping track of collisions (for benchmarking purposes) and a load factor. *The load factor* measures how full the hash map is. We don’t want to be fuller than 75%. If the HashMap is getting too full, we will fix it doing a *rehash* (more on that later). ====== Inserting elements in a HashMap @@ -225,9 +225,9 @@ include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=set, indent=0] <1> Key doesn’t exist yet, so we create the new key/value pair. <2> Key already exists, then we will replace the value. <3> Key doesn’t exist, but the bucket already has other data, this is a collision! We push the new element to the bucket. -<4> To keep insertion order, we keep track of the order of the keys using `keysTrackerArray` and `keysTrackerIndex`. +<4> To keep insertion order, we keep track of the keys' order using `keysTrackerArray` and `keysTrackerIndex`. -Notice, that we are using a function called `getEntry` to check if the key already exists. It gets the index of the bucket corresponding to the key and then checks if the entry with the given key exists. We are going to implement this function in a bit. +Notice that we are using a function called `getEntry` to check if the key already exists. It gets the bucket's index corresponding to the access and then checks if the entry with the given key exists. We are going to implement this function in a bit. ====== Getting values out of a HashMap @@ -243,7 +243,7 @@ include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=getEntry, inde <3> Use Linked list's <> method to find value on the bucket. <4> Return `bucket` and `entry` if found. -With the help of the `getEntry` method, we can do the `HashMap.get` and `HashMap.has` methods: +With the `getEntry` method, we can do the `HashMap.get` and `HashMap.has` methods: .HashMap's get method [source, javascript] @@ -277,7 +277,7 @@ method. ===== Rehashing a HashMap -Rehashing is a technique to minimize collisions when a hash map is getting full. It doubles the size of the map and recomputes all the hash codes and insert data in the new buckets. +Rehashing is a technique to minimize collisions when a hash map is getting full. It doubles the map's size and recomputes all the hash codes, and inserts data in the new buckets. When we increase the map size, we try to find the next prime. We explained that keeping the bucket size a prime number is beneficial for minimizing collisions. @@ -292,7 +292,7 @@ https://github.com/amejiarosario/dsa.js/blob/7694c20d13f6c53457ee24fbdfd3c0ac571 ===== HashMap time complexity -Hash Map it’s very optimal for searching values by key in constant time *O(1)*. However, searching by value is not any better than an array since we have to visit every value *O(n)*. +Hash Map is optimal for searching values by key in constant time *O(1)*. However, searching by value is not any better than an array since we have to visit every value *O(n)*. (((Tables, Non-Linear DS, HashMap complexities))) // tag::table[] @@ -307,4 +307,74 @@ Hash Map it’s very optimal for searching values by key in constant time *O(1)* // end::table[] indexterm:[Runtime, Linear] -As you can notice we have amortized times since, in the unfortunate case of a rehash, it will take O(n) while it resizes. After that, it will be *O(1)*. +As you can notice, we have amortized times since it will take O(n) while it resizes in the unfortunate case of a rehash. After that, it will be *O(1)*. + + +==== Practice Questions +(((Interview Questions, Hash Map))) + + + +// tag::hashmap-q-two-sum[] +===== Fit 2 movies in a flight + +*HM-1*) _You are working in an entertainment recommendation system for an airline. Given a flight duration (target) and an array of movies length, you need to recommend two movies that fit exactly the length of the flight. Return an array with the indices of the two numbers that add up to the target. No duplicates are allowed. If it's not possible to return empty `[]`._ + +// end::hashmap-q-two-sum[] + +// _Seen in interviews at: Amazon, Google, Apple._ + +Examples: + +[source, javascript] +---- +twoSum([113, 248, 80, 200, 91, 201, 68], 316); // [1, 6] (248 + 68 = 316) +twoSum([150, 100, 200], 300); // [2, 3] (100 + 200 = 300) +twoSum([150, 100, 200], 150); // [] (No two numbers add up to 150) +---- + +Starter code: + +[source, javascript] +---- +include::../../interview-questions/two-sum.js[tags=description;placeholder] +---- + + +_Solution: <>_ + + + + + +// tag::hashmap-q-subarray-sum-equals-k[] +===== Subarray Sum that Equals K + +*HM-2*) _Given an array of integers, find all the possible subarrays to add up to k. Return the count._ + +// end::hashmap-q-subarray-sum-equals-k[] + +// _Seen in interviews at: Facebook, Google, Amazon_ + +Examples: + +[source, javascript] +---- +subarraySum([1], 1); // 1 (1 equals to 1 :) +subarraySum([1, 1, 1], 1); // 3 ([1], [1], [1] equals 1) +subarraySum([1, -1, 1], 0); // 2 (sum([1, -1]), sum([-1, 1]) equals 0) +subaraySum([1, 2, 3, 0, 1, 4, 0, 5], 5) // 8 +// All of these 8 sub arrays add up to 5: +// [2, 30], [2,3,0], [0,1,4], [0,1,4,0], [1,4], [1,4,0], [0,5], [5] +---- + +Starter code: + +[source, javascript] +---- +include::../../interview-questions/subarray-sum-equals-k.js[tags=description;placeholder] +---- + + +_Solution: <>_ + diff --git a/book/interview-questions/longest-substring-without-repeating-characters.js b/book/interview-questions/longest-substring-without-repeating-characters.js new file mode 100644 index 00000000..3da6aac9 --- /dev/null +++ b/book/interview-questions/longest-substring-without-repeating-characters.js @@ -0,0 +1,14 @@ +// https://leetcode.com/problems/longest-substring-without-repeating-characters/submissions/ + +function lengthOfLongestSubstring(s: string): number { + let max = 0; + const set = new Set(); + + for (let i = 0, j = 0; j < s.length; j++) { + while (set.has(s[j])) set.delete(s[i++]); + set.add(s[j]); + max = Math.max(max, set.size); + } + + return max; +}; diff --git a/book/interview-questions/longest-substring-without-repeating-characters.spec.js b/book/interview-questions/longest-substring-without-repeating-characters.spec.js new file mode 100644 index 00000000..c56ff203 --- /dev/null +++ b/book/interview-questions/longest-substring-without-repeating-characters.spec.js @@ -0,0 +1,5 @@ +describe('', () => { + it('', () => { + + }); +}); diff --git a/book/interview-questions/subarray-sum-equals-k.js b/book/interview-questions/subarray-sum-equals-k.js new file mode 100644 index 00000000..42db8545 --- /dev/null +++ b/book/interview-questions/subarray-sum-equals-k.js @@ -0,0 +1,58 @@ +// tag::description[] +function subarraySum(nums, k) { + // end::description[] + // tag::placeholder[] + // write your code here... + // end::placeholder[] + // tag::solution[] + let ans = 0; + let sum = 0; + const map = new Map([[0, 1]]); + + for (let i = 0; i < nums.length; i++) { + sum += nums[i]; + if (map.has(sum - k)) ans += map.get(sum - k); + map.set(sum, 1 + (map.get(sum) || 0)); + } + + return ans; + // end::solution[] + // tag::description[] +} +// end::description[] + +// tag::subarraySumBrute1[] +function subarraySumBrute1(nums, k) { + let ans = 0; + + for (let i = 0; i < nums.length; i++) { + for (let j = i; j < nums.length; j++) { + let sum = 0; + for (let n = i; n <= j; n++) { + sum += nums[n]; + } + if (sum === k) ans++; + } + } + + return ans; +} +// end::subarraySumBrute1[] + +// tag::subarraySumBrute2[] +function subarraySumBrute2(nums, k) { + let ans = 0; + + for (let i = 0; i < nums.length; i++) { + let sum = 0; + for (let j = i; j < nums.length; j++) { + sum += nums[j]; + if (sum === k) ans++; + } + } + + return ans; +} +// end::subarraySumBrute2[] + +module.exports = { subarraySum, subarraySumBrute1, subarraySumBrute2 }; diff --git a/book/interview-questions/subarray-sum-equals-k.spec.js b/book/interview-questions/subarray-sum-equals-k.spec.js new file mode 100644 index 00000000..c3670295 --- /dev/null +++ b/book/interview-questions/subarray-sum-equals-k.spec.js @@ -0,0 +1,24 @@ +const { subarraySum, subarraySumBrute1, subarraySumBrute2 } = require('./subarray-sum-equals-k'); +// const { } = require('../../src/index'); + +[subarraySum, subarraySumBrute1, subarraySumBrute2].forEach((fn) => { + describe(`HashMap: ${fn.name}`, () => { + it('should work with null/empty', () => { + const actual = []; + const expected = 0; + expect(fn(actual, 0)).toEqual(expected); + }); + + it('should work with small case', () => { + const actual = [1, -1, 1]; + const expected = 3; + expect(fn(actual, 1)).toEqual(expected); + }); + + it('should work with other case', () => { + const actual = [1, 2, 3, 0, 1, 4, 0, 5]; + const expected = 8; + expect(fn(actual, 5)).toEqual(expected); + }); + }); +}); diff --git a/book/interview-questions/two-sum.js b/book/interview-questions/two-sum.js new file mode 100644 index 00000000..985585ec --- /dev/null +++ b/book/interview-questions/two-sum.js @@ -0,0 +1,34 @@ +// tag::description[] +function twoSum(nums, target) { + // end::description[] + // tag::placeholder[] + // write your code here... + // end::placeholder[] + // tag::solution[] + const map = new Map(); + + for (let i = 0; i < nums.length; i++) { + const complement = target - nums[i]; + if (map.has(nums[i])) return [map.get(nums[i]), i]; + map.set(complement, i); + } + + return []; + // end::solution[] + // tag::description[] +} +// end::description[] + +// tag::twoSumBrute[] +function twoSumBrute(nums, target) { + for (let i = 0; i < nums.length; i++) { + for (let j = i + 1; j < nums.length; j++) { + if (nums[i] + nums[j] === target) return [i, j]; + } + } + + return []; +} +// end::twoSumBrute[] + +module.exports = { twoSum, twoSumBrute }; diff --git a/book/interview-questions/two-sum.spec.js b/book/interview-questions/two-sum.spec.js new file mode 100644 index 00000000..7507ac18 --- /dev/null +++ b/book/interview-questions/two-sum.spec.js @@ -0,0 +1,28 @@ +const { twoSum } = require('./two-sum'); +// const { } = require('../../src/index'); + +describe('HashMap: Two Sum', () => { + it('should work with null/empty', () => { + const actual = []; + const expected = []; + expect(twoSum(actual, 0)).toEqual(expected); + }); + + it('should work with small case', () => { + const actual = [150, 100, 200]; + const expected = [1, 2]; + expect(twoSum(actual, 300)).toEqual(expected); + }); + + it('should work with small invalid case', () => { + const actual = [150, 100, 200]; + const expected = []; + expect(twoSum(actual, 150)).toEqual(expected); + }); + + it('should work with other case', () => { + const actual = [113, 248, 80, 200, 91, 201, 68]; + const expected = [1, 6]; + expect(twoSum(actual, 316)).toEqual(expected); + }); +});