diff --git a/book/D-interview-questions-solutions.asc b/book/D-interview-questions-solutions.asc index 7c0c2ad5..5fe1acf1 100644 --- a/book/D-interview-questions-solutions.asc +++ b/book/D-interview-questions-solutions.asc @@ -1,3 +1,4 @@ +<<< [appendix] [[d-interview-questions-solutions]] == Interview Questions Solutions @@ -29,7 +30,7 @@ include::interview-questions/max-subarray.js[tag=maxSubArrayBrute1] This code is simple to understand; however, not very efficient. The runtime is `O(n^3)`. -If you noticed we adding up the numbers from `i` to `j` on each cycle. But, we can optimize this. We can keep a local variable and add the new number to it. That way, we don't have to revisit previous numbers. +Notice we're adding up the numbers from `i` to `j` on each cycle. But, we can optimize this. We can keep a local variable and add the new number to it. That way, we don't have to revisit previous numbers. [source, javascript] ---- @@ -46,7 +47,7 @@ include::interview-questions/max-subarray.js[tag=description] include::interview-questions/max-subarray.js[tag=solution] ---- -The runtime is `O(n)` and a space complexity of `O(1)`. +The runtime is `O(n)` and space complexity of `O(1)`. @@ -93,4 +94,203 @@ include::interview-questions/buy-sell-stock.js[tag=description] include::interview-questions/buy-sell-stock.js[tag=solution] ---- -The runtime is `O(n)` and a space complexity of `O(1)`. +The runtime is `O(n)` and space complexity of `O(1)`. + + + +:leveloffset: +1 + +=== Solutions for Linked List Questions +(((Interview Questions Solutions, Linked Lists))) + +:leveloffset: -1 + + + + +[#linkedlist-q-merge-lists] +include::content/part02/linked-list.asc[tag=linkedlist-q-merge-lists] + +We need to visit each node in both lists and merge them in ascending order. Note: We don't need to copy the values nor create new nodes. + +Another case to take into consideration is that lists might have different lengths. So, if one list runs out, we have to keep taking elements from the remaining list. + +*Algorithm*: + +- Have a pointer for each list +- While there's a pointer that is not null, visite them + - Compare each list node's value and take the smaller one. + - Advance the pointer of the taken node to the next one. + +*Implementation*: + +[source, javascript] +---- +include::interview-questions/merge-lists.js[tag=description] +include::interview-questions/merge-lists.js[tag=solution] +---- + +Notice that we used a "dummy" node or "sentinel node" to have some starting point for the final list. + +*Complexity Analysis*: + +- Time: `O(m+n)`. Visiting each node from the list 1 and list 2 has a time complexity `O(m + n)`. `m` and `n` represent each list's length. +- Space: `O(1)`. We reuse the same nodes and only change their `next` pointers. We only create one additional node, "the sentinel node." + + +[#linkedlist-q-linkedlist-same-data] +include::content/part02/linked-list.asc[tag=linkedlist-q-linkedlist-same-data] + +We are given two linked lists that contain string data. We want to know if the concatenated strings from each list are the same. + +The tricky part is that the same data can be distributed differently on the linked lists: + +---- +L1: he -> ll -> o +L2: h -> e -> llo +---- + +One naive approach could be to go through each list's node and concatenate the strings. Then, we can check if they are equal. + +[source, javascript] +---- +include::interview-questions/linkedlist-same-data.js[tag=hasSameDataBrute1] +---- + +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. + +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. + +*Algorithm*: + +- Set a pointer to iterate over each node in the lists. +- For each node, have an index (starting at zero) and compare if both lists have the same data. + - When the index reaches the last character on the current node, we move to the next node. + - If we found that a character from one list doesn't match the other, we return `false`. + +*Implementation*: + +[source, javascript] +---- +include::interview-questions/linkedlist-same-data.js[tag=description] +include::interview-questions/linkedlist-same-data.js[tag=solution] +---- + +The function `findNextPointerIndex` is a helper to navigate each character on a linked list. +Notice, that we increase the index (`i + 1`) on each iteration. +If the index overflows, it moves to the next node and reset the index to zero. + + + +*Complexity Analysis*: + +- Time: `O(n)`. We go over all the characters on each list +- Space: `O(1)`. Only using pointers and no auxiliary data structures. + + + +:leveloffset: +1 + +=== Solutions for Stack Questions +(((Interview Questions Solutions, Stack))) + +:leveloffset: -1 + +[#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: +- 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. + +*Algorithm*: + +- Create a mapping for each opening bracket, to its closing counterpart. +- Iterate through the string + - When we found an opening bracket, insert the corresponding closing bracket into the stack. + - When we found a closing bracket, pop from the stack and make sure it corresponds to the current character. +- Check the stack is empty. If there's a leftover, it means that something didn't close properly. + +*Implementation*: + +[source, javascript] +---- +include::interview-questions/valid-parentheses.js[tag=description] +include::interview-questions/valid-parentheses.js[tag=solution] +---- + +*Complexity Analysis*: + +- Time: `O(n)`. We iterate over each character of the string. +- Space: `O(n)`. We use an auxiliary stack. + + + +[#stack-q-daily-temperatures] +include::content/part02/stack.asc[tag=stack-q-daily-temperatures] + +The first solution that might come to mind it's using two for loops. For each element, we have visit each temperature ahead to find a bigger one. + +[source, javascript] +---- +include::interview-questions/daily-temperatures.js[tag=dailyTemperaturesBrute1] +---- + +This solution is an `O(n^2)`. Can we do better? We can! + +Here's an idea: start backward, so we know when there's a warmer temperature beforehand. The last element is always 0 (because there are no more temperatures ahead of it). We can place each element's index that we visit on a stack. If the current weather is bigger than the stack top, we remove it until a bigger one remains or the stack is empty. If the stack has a value, we calculate the number of days ahead. Otherwise, it is 0. + +*Algorithm*: + +- Traverse the daily temperatures backward + - Push each temperature to a stack. + - While the current temperature is larger than the one at the top of the stack, pop it. + - If the stack is empty, then there's no warmer weather ahead, so it's 0. + - If the stack has an element, calculate the index delta. + +*Implementation*: + +[source, javascript] +---- +include::interview-questions/daily-temperatures.js[tag=description] +include::interview-questions/daily-temperatures.js[tag=solution] +---- + +The stack contains the indexes rather than the temperatures themselves. + +*Complexity Analysis*: + +- Time: `O(n)`. We visit each element on the array once. +- Space: `O(1)`. The worst-case scenario is ascending order without duplicates. The stack will hold at most 70 items (100 - 30). If we didn't have the range restriction, then space complexity would be `O(n)`. + + + +// [#linkedlist-q-FILENAME] +// include::content/part02/linked-list.asc[tag=linkedlist-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? + diff --git a/book/config b/book/config index 1216accf..b4dd20d3 160000 --- a/book/config +++ b/book/config @@ -1 +1 @@ -Subproject commit 1216accfbd750b835ba92c196aa20ddd3414c515 +Subproject commit b4dd20d3f92beb4dd48445464e1734819a9cd40a diff --git a/book/content/part02/array.asc b/book/content/part02/array.asc index 4b2c2c7e..e03863e5 100644 --- a/book/content/part02/array.asc +++ b/book/content/part02/array.asc @@ -7,18 +7,18 @@ endif::[] === Array [[array-chap]] (((Array))) (((Data Structures, Linear, Array))) -Arrays are one of the most used data structures. You probably have used it a lot but are you aware of the runtimes of `splice`, `shift`, `indexOf` and other operations? In this chapter, we are going deeper into the most common operations and their runtimes. +Arrays are one of the most used data structures. You probably have used it a lot, but are you aware of the runtimes of `splice`, `shift`, `indexOf`, and other operations? In this chapter, we are going deeper into the most common operations and their runtimes. ==== Array Basics An array is a collection of things (strings, characters, numbers, objects, etc.). They can be many or zero. -TIP: Strings are a collection of Unicode characters and most of the array concepts apply to them. +TIP: Strings are a collection of Unicode characters, and most of the array concepts apply to them. .Fixed vs. Dynamic Size Arrays **** -Some programming languages have fixed size arrays like Java and {cpp}. -Fixed size arrays might be a hassle when your collection gets full, and you have to create a new one with a bigger size. For that, those programming languages also have built-in dynamic arrays: we have `vector` in {cpp} and `ArrayList` in Java. Dynamic programming languages like JavaScript, Ruby, and Python use dynamic arrays by default. +Some programming languages have fixed-size arrays like Java and {cpp}. +Fixed-size arrays might be a hassle when your collection gets full, and you have to create a new one with a bigger size. Those programming languages also have built-in dynamic arrays: we have `vector` in {cpp} and `ArrayList` in Java. Dynamic programming languages like JavaScript, Ruby, and Python use dynamic arrays by default. **** Arrays look like this: @@ -26,11 +26,11 @@ Arrays look like this: .Array representation: each value is accessed through an index. image::image16.png[image,width=388,height=110] -Arrays are a sequential collection of elements that can be accessed randomly using an index. Let’s take a look into the different operations that we can do with arrays. +Arrays are a sequential collection of elements that can be accessed randomly using an index. Let’s take a look at the different operations that we can do with arrays. ==== Insertion -Arrays are built-in into most languages. Inserting an element is simple; you can either add them at creation time or after initialization. Below you can find an example for both cases: +Arrays are built-in in most languages. Inserting an element is simple; you can either add them at creation time or after initialization. Below you can find an example for both cases: .Inserting elements into an array [source, javascript] @@ -45,7 +45,7 @@ array2[100] = 2; array2 // [empty × 3, 1, empty × 96, 2] ---- -Using the index, you can replace whatever value you want. Also, you don't have to add items next to each other. The size of the array will dynamically expand to accommodate the data. You can reference values at whatever index you like: index 3 or even 100! In `array2`, we inserted 2 numbers but the length is 101 and there are 99 empty spaces. +Using the index, you can replace whatever value you want. Also, you don't have to add items next to each other. The size of the array will dynamically expand to accommodate the data. You can reference values at whatever index you like: index 3 or even 100! In `array2`, we inserted 2 numbers, but the length is 101, and there are 99 empty spaces. [source, javascript] ---- @@ -54,7 +54,7 @@ console.log(array2); // [empty × 3, 1, empty × 96, 2] ---- -The runtime for inserting elements using index is always is constant: _O(1)_. +The runtime for inserting elements using an index is always is constant: _O(1)_. ===== Inserting at the beginning of the array @@ -72,7 +72,7 @@ As you can see, `2` was at index 0, now was pushed to index 1, and everything el .JavaScript built-in `array.unshift` **** -The `unshift()` method adds one or more elements to the beginning of an array and returns the new length of the array. +The `unshift()` method adds one or more elements to the beginning of an array and returns the array's new length. Runtime: O(n). **** @@ -90,11 +90,11 @@ array.splice(1, 0, 111); // ↪️ [] <1> ---- <1> at position `1`, delete `0` elements and insert `111`. -The Big O for this operation would be *O(n)* since in worst case it would move most of the elements to the right. +The Big O for this operation would be *O(n)* since, in the worst case, it would move most of the elements to the right. .JavaScript built-in `array.splice` **** -The `splice()` method changes the contents of an array by removing existing elements or adding new elements. Splice returns an array containing the deleted elements. +The `splice()` method changes an array's contents by removing existing elements or adding new elements. Splice returns an array containing the deleted items. Runtime: O(n). **** @@ -116,7 +116,7 @@ Adding to the tail of the array doesn’t change other indexes. E.g., element 2 .JavaScript built-in `array.push` **** -The `push()` method adds one or more elements to the end of an array and returns the new length of the array. +The `push()` method adds one or more elements to the end of an array and returns the array's new length. Runtime: O(1). **** @@ -124,7 +124,7 @@ Runtime: O(1). [[array-search-by-value]] ==== Searching by value and index -Searching by index is very easy using the `[]` operator: +Searching by the index is very easy using the `[]` operator: .Search by index [source, javascript] @@ -185,7 +185,7 @@ We would have to loop through the whole array (worst case) or until we find it: ==== Deletion -There are three possible scenarios for deletion (similar to insertion): removing at the beginning, middle or end. +There are three possible deletion scenarios (similar to insertion): removing at the beginning, middle, or end. ===== Deleting element from the beginning @@ -224,7 +224,7 @@ array.splice(2, 1); // ↪️[2] <1> ---- <1> delete 1 element at position 2 -Deleting from the middle might cause most of the elements of the array to move up one position to fill in for the eliminated item. Thus, runtime: O(n). +Deleting from the middle might cause most of the array elements to move up one position to fill in for the eliminated item. Thus, runtime: O(n). ===== Deleting element from the end @@ -282,7 +282,7 @@ To sum up, the time complexity of an array is: // tag::array-q-max-subarray[] ===== Max Subarray -Given an array of integers, find the maximum sum of consecutive elements (subarray). +*AR-1*) _Given an array of integers, find the maximum sum of consecutive elements (subarray)._ // end::array-q-max-subarray[] [source, javascript] @@ -297,7 +297,7 @@ _Solution: <>_ // tag::array-q-buy-sell-stock[] ===== Best Time to Buy and Sell an Stock -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 maximun profit you can obtain? (Note: you have to buy first and then sell) +*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)_ // end::array-q-buy-sell-stock[] [source, javascript] diff --git a/book/content/part02/linked-list.asc b/book/content/part02/linked-list.asc index 6e3f3f98..7790c3e4 100644 --- a/book/content/part02/linked-list.asc +++ b/book/content/part02/linked-list.asc @@ -23,7 +23,7 @@ Each element or node is *connected* to the next one by a reference. When a node .Singly Linked List Representation: each node has a reference (blue arrow) to the next one. image::image19.png[image,width=498,height=97] -Usually, a Linked List is referenced by the first element called *head* (or *root* node). For instance, if you want to get the `cat` element from the example above, then the only way to get there is using the `next` field on the head node. You would get `art` first, then use the next field recursively until you eventually get the `cat` element. +Usually, a Linked List is referenced by the first element called *head* (or *root* node). For instance, if you want to get the `cat` element from the example above, then the only way to get there is by using the `next` field on the head node. You would get `art` first, then use the next field recursively until you eventually get the `cat` element. [[doubly-linked-list]] ==== Doubly Linked List @@ -47,9 +47,9 @@ include::{codedir}/data-structures/linked-lists/node.js[tag=snippet] ==== Linked List vs. Array -Arrays allow you to access data anywhere in the collection using an index. However, Linked List visits nodes in sequential order. In the worst case scenario, it takes _O(n)_ to get an element from a Linked List. You might be wondering: Isn’t an array always more efficient with _O(1)_ access time? It depends. +Arrays allow you to access data anywhere in the collection using an index. However, Linked List visits nodes in sequential order. In the worst-case scenario, it takes _O(n)_ to get an element from a Linked List. You might be wondering: Isn’t an array always more efficient with _O(1)_ access time? It depends. -We also have to understand the space complexity to see the trade-offs between arrays and linked lists. An array pre-allocates contiguous blocks of memory. When it is getting full, it has to create a bigger array (usually 2x) and copy all the elements. It takes _O(n)_ to copy all the items over. On the other hand, LinkedList’s nodes only reserve precisely the amount of memory they need. They don’t have to be next to each other, nor large chunks of memory have to be booked beforehand like arrays. Linked List is more on a "grow as you go" basis. +We also have to understand the space complexity to see the trade-offs between arrays and linked lists. An array pre-allocates contiguous blocks of memory. It has to create a larger array (usually 2x) and copy all the elements when it is getting full. It takes _O(n)_ to copy all the items over. On the other hand, LinkedList’s nodes only reserve precisely the amount of memory they need. They don’t have to be next to each other, nor are large chunks of memory booked beforehand like arrays. Linked List is more on a "grow as you go" basis. Another difference is that adding/deleting at the beginning on an array takes O(n); however, the linked list is a constant operation O(1) as we will implement later. @@ -70,7 +70,7 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=constructor] } ---- -In our constructor, we keep a reference of the `first` and also `last` node for performance reasons. +In our constructor, we reference the `first` and `last` node for performance reasons. ==== Searching by value @@ -84,7 +84,7 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=searchByValue If we find the element, we will return the index otherwise `undefined`. The runtime for locating an item by value is _O(n)_. -For finding elements by value or position we are using the following helper function: +For finding elements by value or position, we are using the following helper function: .Find elements using a callback [source, javascript] @@ -92,8 +92,8 @@ For finding elements by value or position we are using the following helper func include::{codedir}/data-structures/linked-lists/linked-list.js[tag=find, indent=0] ---- <1> We initialize two variables `current` to the first node and `position` to keep track of the index. -<2> While `current` node is not null we keep going. -<3> On each loop we move to the next node and increment the index. +<2> While the `current` node is not null, we keep going. +<3> On each loop, we move to the next node and increment the index. <4> We invoke the callback passing the current position and node. If the callback returns something, then we stop and return that value. <5> Return whatever result we got from the callback. E.g., we can return the index or the node itself or any other calculation. @@ -101,7 +101,7 @@ We are going to use this `find` method again to implement searching by index. ==== Searching by index -Searching by index is very similar, we iterate through the list until we find the element that matches the position. +Searching by an index is very similar, we iterate through the list until we find the element that matches the position. .Linked List's searching by index (position) [source, javascript] @@ -109,22 +109,22 @@ Searching by index is very similar, we iterate through the list until we find th include::{codedir}/data-structures/linked-lists/linked-list.js[tag=searchByIndex, indent=0] ---- -If there’s no match, we return `undefined` then. The runtime is _O(n)_. As you might notice, the search by index and by position methods looks pretty similar. If you want to take a look at the whole implementation, https://github.com/amejiarosario/dsa.js/blob/7694c20d13f6c53457ee24fbdfd3c0ac57139ff4/src/data-structures/linked-lists/linked-list.js#L8[click here]. +If there’s no match, we return `undefined` then. The runtime is _O(n)_. As you might notice, the search by index and by position methods looks pretty similar; you want to take a look at the https://github.com/amejiarosario/dsa.js/blob/7694c20d13f6c53457ee24fbdfd3c0ac57139ff4/src/data-structures/linked-lists/linked-list.js#L8[full implementation]. ==== Insertion -Similar to the array, with a linked list you can add elements at the beginning, end or anywhere in the middle of the list. So, let's implement each case. +In a linked list, you can add elements at the beginning, end, or anywhere in the middle of the list. So, let's implement each case. [[linked-list-inserting-beginning]] ===== Inserting elements at the beginning of the list -We are going to use the `Node` class to create a new element and stick it at the beginning of the list as shown below. +We will use the `Node` class to create a new element and stick it at the beginning of the list, as shown below. .Insert at the beginning by linking the new node with the current first node. image::image23.png[image,width=498,height=217] -To insert at the beginning, we create a new node with the next reference to the current first node. Then we make first the new node. In code, it would look something like this: +To insert at the beginning, we create a new node with the next reference to the current first node. Then we first make the new node. In code, it would look something like this: .Add item to the beginning of a Linked List [source, javascript] @@ -137,7 +137,7 @@ As you can see, we create a new node and make it the first one. ===== Inserting element at the end of the list -Appending an element at the end of the list can be done very effectively if we have a pointer to the `last` item in the list. Otherwise, you would have to iterate through the whole list. +Appending an element at the end of the list can be done very effectively if we have a pointer to the `last` item. Otherwise, you would have to iterate through the whole list. .Add element to the end of the linked list image::image24.png[image,width=498,height=208] @@ -148,12 +148,12 @@ image::image24.png[image,width=498,height=208] include::{codedir}/data-structures/linked-lists/linked-list.js[tag=addLast, indent=0] ---- -If there’s no element in the list yet, the first and last node would be the same. If there’s something, then, we go to the `last` item and add the reference `next` to the new node. That’s it! We got a constant time for inserting at the beginning and the end of the list: *O(1)*. +If there’s no element in the list yet, the first and last node would be the same. If there’s something, we go to the `last` item and add the reference `next` to the new node. That’s it! We got a constant time for inserting at the beginning and the end of the list: *O(1)*. ===== Inserting element at the middle of the list -For inserting an element at the middle of the list, you would need to specify the position (index) in the collection. Then, you create the new node and update the references to it. +For inserting an element in the middle of the list, you would need to specify the position (index) in the collection. Then, you create the new node and update the references to it. .There are 4 references to update: . New node's `next`. @@ -168,7 +168,7 @@ Let’s do an example with the following doubly linked list: art <-> dog <-> cat ---- -We want to insert the `new` node in the 2^nd^ position. For that we first create the "new" node and update the references around it. +We want to insert the `new` node in the 2^nd^ position. For that, we first create the "new" node and update the references around it. .Inserting node in the middle of a doubly linked list. image::image25.png[image,width=528,height=358] @@ -181,11 +181,11 @@ Take a look into the implementation of https://github.com/amejiarosario/dsa.js/b include::{codedir}/data-structures/linked-lists/linked-list.js[tag=addMiddle, indent=0] ---- <1> If the new item goes to position 0, then we reuse the `addFirst` method, and we are done! -<2> However, if we are adding to the last position, then we reuse the `addLast` method, and done! -<3> Adding `newNode` to the middle: First, create the `new` node only if the position exists. Take a look at <> to see `get` implementation. +<2> However, if we add to the last position, we reuse the `addLast` method and done! +<3> Adding `newNode` to the middle: First, create the `new` node only if it exists. Take a look at <> to see `get` implementation. <4> Set newNode `previous` reference. <5> Set newNode `next` link. -<6> No other node in the list is pointing to `newNode`, so we have to make the prior element point to `newNode`. +<6> No other node in the list points to `newNode`, so we have to make the prior element point to `newNode`. <7> Make the next element point to `newNode`. Take notice that we reused `addFirst` and `addLast` methods. For all the other cases, the insertion is in the middle. We use `current.previous.next` and `current.next` to update the surrounding elements and make them point to the new node. Inserting in the middle takes *O(n)* because we have to iterate through the list using the `get` method. @@ -219,7 +219,7 @@ Removing the last element from the list would require to iterate from the head u image::image27.png[image,width=528,height=221] -For instance, if we want to remove the last node “cat”. We use the last pointer to avoid iterating through the whole list. We check `last.previous` to get the “dog” node and make it the new `last` and remove its next reference to “cat”. Since nothing is pointing to “cat”, it is out of the list and eventually is deleted from memory by the garbage collector. +For instance, if we want to remove the last node “cat”. We use the last pointer to avoid iterating through the whole list. We check `last.previous` to get the “dog” node and make it the new `last` and remove its next reference to “cat.” Since nothing is pointing to “cat” it is out of the list and eventually is deleted from memory by the garbage collector. .Linked List's remove from the end of the list [source, javascript] @@ -228,7 +228,7 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=removeLast, i ---- -The code is very similar to `removeFirst`, but instead of first we update `last` reference, and instead of nullifying `previous` we nullify its `next` reference. +The code is very similar to `removeFirst`, but instead of first, we update `last` reference, and instead of nullifying `previous`, we nullify its `next` reference. ===== Deleting element from the middle @@ -238,7 +238,7 @@ To remove a node from the middle, we make the surrounding nodes to bypass the on image::image28.png[image,width=528,height=259] -In the illustration, we are removing the middle node “dog” by making art’s `next` variable to point to cat and cat’s `previous` to be “art”, totally bypassing “dog”. +In the illustration, we are removing the middle node “dog” by making art’s `next` variable to point to cat and cat’s `previous` to be “art,” totally bypassing “dog.” Let’s implement it: @@ -252,7 +252,7 @@ Notice that we are using the `get` method to get the node at the current positio ==== Linked List Complexity vs. Array Complexity -So far, we have seen two liner data structures with different use cases. Here’s a summary: +So far, we have seen two-liner data structures with different use cases. Here’s a summary: (((Tables, Linear DS, Array/Lists complexities))) // tag::table[] @@ -268,9 +268,9 @@ So far, we have seen two liner data structures with different use cases. Here’ (((Linear))) (((Runtime, Linear))) -If you compare the singly linked list vs. doubly linked list, you will notice that the main difference is inserting elements to and deleting elements from the end. For a singly linked list, it's *O(n)*, while a doubly linked list is *O(1)*. +If you compare the singly linked list vs. doubly linked list, you will notice that the main difference is inserting elements to and deleting elements from the end. For a singly linked list, it's *O(n)*, while a doubly-linked list is *O(1)*. -Comparing an array with a doubly linked list, both have different use cases: +Comparing an array with a doubly-linked list, both have different use cases: Use arrays when: @@ -282,10 +282,45 @@ Use a doubly linked list when: * You want to access elements in a *sequential* manner only like <> or <>. * You want to insert elements at the start and end of the list. The linked list has O(1) while array has O(n). -* You want to save some memory when dealing with possibly large data sets. Arrays pre-allocate a large chunk of contiguous memory on initialization. Lists are more “grow as you go”. +* You want to save some memory when dealing with possibly large data sets. Arrays pre-allocate a large chunk of contiguous memory on initialization. Lists are more “grow as you go.” -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 are going use that. +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. -==== Linked List Exercises +==== Interview Questions +(((Interview Questions, Arrays))) -1) Merge two sorted lists into one (and keep them sorted) + + + +// tag::linkedlist-q-merge-lists[] +===== Merge Linked Lists into One + +*LL-1*) _Merge two sorted lists into one (and keep them sorted)_ +// end::linkedlist-q-merge-lists[] + +[source, javascript] +---- +include::../../interview-questions/merge-lists.js[tag=description] + // write you code here +} +---- + +_Solution: <>_ + + + + +// tag::linkedlist-q-linkedlist-same-data[] +===== Check if two strings lists are the same + +*LL-2*) _Given two linked lists with strings, check if are the same_ +// end::linkedlist-q-linkedlist-same-data[] + +[source, javascript] +---- +include::../../interview-questions/linkedlist-same-data.js[tag=description] + // write you code here +} +---- + +_Solution: <>_ diff --git a/book/content/part02/stack.asc b/book/content/part02/stack.asc index 81ced6f2..02643814 100644 --- a/book/content/part02/stack.asc +++ b/book/content/part02/stack.asc @@ -11,7 +11,7 @@ endif::[] (((LIFO))) The stack is a data structure that restricts the way you add and remove data. It only allows you to insert and retrieve in a *Last-In-First-Out* (LIFO) fashion. -An analogy is to think that the stack is a rod and the data are discs. You can only take out the last one you put in. +An analogy is to think that the stack is a rod, and the data are discs. You can only take out the last one you put in. .Stack data structure is like a stack of disks: the last element in is the first element out image::image29.png[image,width=240,height=238] @@ -20,7 +20,7 @@ image::image29.png[image,width=240,height=238] As you can see in the image above, If you insert the disks in the order `5`, `4`, `3`, `2`, `1`, then you can remove them in `1`, `2`, `3`, `4`, `5`. -The stack inserts items to the end of the collection and also removes from the end. Both an array and linked list would do it in constant time. However, since we don’t need the Array’s random access, a linked list makes more sense. +The stack inserts items to the end of the collection and also removes it from the rear. Both an array and linked list would do it in constant time. However, since we don’t need the Array’s random access, a linked list makes more sense. .Stack's constructor [source, javascript] @@ -30,7 +30,7 @@ include::{codedir}/data-structures/stacks/stack.js[tag=constructor] } ---- -As you can see in the stack constructor, we are using a linked list as the underlying data structure. +As you can see in the stack constructor, we use a linked list as the underlying data structure. Let's now develop the insert and remove operations in a stack. @@ -48,7 +48,7 @@ We are returning `this`, in case we want to chain multiple add commands. ==== Deletion -Deleting is straightforward as well. +Deleting is straightforward, as well. .Stack's remove [source, javascript] @@ -56,7 +56,7 @@ Deleting is straightforward as well. include::{codedir}/data-structures/stacks/stack.js[tag=remove, indent=0] ---- -This time we used the linked list’s `removeLast` method. That’s all we need for a stack implementation. Check out the full implementation https://github.com/amejiarosario/dsa.js/blob/f69b744a1bddd3d99243ca64b3ad46f3f2dd7342/src/data-structures/stacks/stack.js#L6[here]. +This time we used the linked list’s `removeLast` method. That’s all we need for a stack implementation. Check out the https://github.com/amejiarosario/dsa.js/blob/f69b744a1bddd3d99243ca64b3ad46f3f2dd7342/src/data-structures/stacks/stack.js#L6[full implementation]. ==== Implementation Usage @@ -68,7 +68,7 @@ We can use our stack implementation as follows: include::{codedir}/data-structures/stacks/stack.js[tag=snippet, indent=0] ---- -As you can see if we add new items they will be the first to go out to honor LIFO. +As you can see, if we add new items, they will be the first to go out to honor LIFO. ==== Stack Complexity @@ -85,3 +85,45 @@ Implementing the stack with an array and linked list would lead to the same time // end::table[] 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 <>. + + +==== Interview Questions +(((Interview Questions, Arrays))) + + + + + +// tag::stack-q-valid-parentheses[] +===== Validate Parentheses / Braces / Brackets + +*ST-1*) _Given an string with 3 types of brakets: `()`, `{}`, and `[]`. Validate they are properly closed and opened._ +// end::stack-q-valid-parentheses[] + +[source, javascript] +---- +include::../../interview-questions/valid-parentheses.js[tag=description] + // write you code here +} +---- + +_Solution: <>_ + + + + + +// tag::stack-q-daily-temperatures[] +===== Daily Temperaturs + +*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._ +// end::stack-q-daily-temperatures[] + +[source, javascript] +---- +include::../../interview-questions/daily-temperatures.js[tag=description] + // write you code here +} +---- + +_Solution: <>_ diff --git a/book/interview-questions/daily-temperatures.js b/book/interview-questions/daily-temperatures.js new file mode 100644 index 00000000..5a4b4613 --- /dev/null +++ b/book/interview-questions/daily-temperatures.js @@ -0,0 +1,48 @@ +// tag::description[] +/** + * Given an array with daily temperatures (30 °C to 100 °C), + * return an array with the days count until a warmer temperature + * for each elem from the input. + * + * @examples + * dailyTemperatures([30, 28, 50, 40, 30]); // [2, 1, 0, 0, 0] + * dailyTemperatures([73, 69, 72, 76, 73]); // [3, 1, 1, 0, 0] + * + * @param {number[]} t - Daily temperatures + */ +function dailyTemperatures(t) { + // end::description[] + // tag::solution[] + const last = (arr) => arr[arr.length - 1]; + const stack = []; + const ans = []; + + for (let i = t.length - 1; i >= 0; i--) { + while (stack.length && t[i] >= t[last(stack)]) stack.pop(); + ans[i] = stack.length ? last(stack) - i : 0; + stack.push(i); + } + + return ans; +} +// end::solution[] + +// tag::dailyTemperaturesBrute1[] +function dailyTemperaturesBrute1(t) { + const ans = []; + + for (let i = 0; i < t.length; i++) { + ans[i] = 0; + for (let j = i + 1; j < t.length; j++) { + if (t[j] > t[i]) { + ans[i] = j - i; + break; + } + } + } + + return ans; +} +// end::dailyTemperaturesBrute1[] + +module.exports = { dailyTemperatures, dailyTemperaturesBrute1 }; diff --git a/book/interview-questions/daily-temperatures.spec.js b/book/interview-questions/daily-temperatures.spec.js new file mode 100644 index 00000000..3ff950d7 --- /dev/null +++ b/book/interview-questions/daily-temperatures.spec.js @@ -0,0 +1,21 @@ +const { dailyTemperatures } = require('./daily-temperatures'); + +describe('Stack: Daily Temperatures', () => { + it('should work', () => { + expect(dailyTemperatures([30, 28, 50, 40, 30])).toEqual([2, 1, 0, 0, 0]); + }); + + it('should work', () => { + expect(dailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73])).toEqual([1, 1, 4, 2, 1, 1, 0, 0]); + }); + + it('should work', () => { + expect(dailyTemperatures([89, 62, 70, 58, 47, 47, 46, 76, 100, 70])).toEqual([8, 1, 5, 4, 3, 2, 1, 1, 0, 0]); + }); + + it('should work with large data', () => { + const input = [64, 40, 49, 73, 72, 35, 68, 83, 35, 73, 84, 88, 96, 43, 74, 63, 41, 95, 48, 46, 89, 72, 34, 85, 72, 59, 87, 49, 30, 32, 47, 34, 74, 58, 31, 75, 73, 88, 64, 92, 83, 64, 100, 99, 81, 41, 48, 83, 96, 92, 82, 32, 35, 68, 68, 92, 73, 92, 52, 33, 44, 38, 47, 88, 71, 50, 57, 95, 33, 65, 94, 44, 47, 79, 41, 74, 50, 67, 97, 31, 68, 50, 37, 70, 77, 55, 48, 30, 77, 100, 31, 100, 69, 60, 47, 95, 68, 47, 33, 64]; + const output = [3, 1, 1, 4, 3, 1, 1, 3, 1, 1, 1, 1, 30, 1, 3, 2, 1, 25, 2, 1, 19, 2, 1, 3, 2, 1, 11, 5, 1, 1, 2, 1, 3, 2, 1, 2, 1, 2, 1, 3, 2, 1, 0, 46, 3, 1, 1, 1, 30, 18, 5, 1, 1, 2, 1, 12, 1, 10, 5, 1, 2, 1, 1, 4, 3, 1, 1, 11, 1, 1, 8, 1, 1, 5, 1, 3, 1, 1, 11, 1, 3, 2, 1, 1, 5, 3, 2, 1, 1, 0, 1, 0, 3, 2, 1, 0, 0, 2, 1, 0]; + expect(dailyTemperatures(input)).toEqual(output); + }); +}); diff --git a/book/interview-questions/linkedlist-same-data.js b/book/interview-questions/linkedlist-same-data.js new file mode 100644 index 00000000..fd303d36 --- /dev/null +++ b/book/interview-questions/linkedlist-same-data.js @@ -0,0 +1,83 @@ +// const ListNode = require('../../src/data-structures/linked-lists/node'); + +// tag::description[] +/** + * Check if two lists has the same string data. + * Note: each lists can be huge, they have up to 10 million nodes. + * + * @examples + * hasSameData(['he', 'll', 'o'], ['hel', 'lo']); // true + * hasSameData(['hel', 'lo'], ['hi']); // false + * + * @param {ListNode} l1 - The root node of list 1 + * @param {ListNode} l2 - The root node of list 2 + */ +function hasSameData(l1, l2) { + // end::description[] + // tag::solution[] + let p1 = l1; + let p2 = l2; + let i1 = -1; + let i2 = -1; + + const findNextPointerIndex = (p, i) => { + let node = p; + let index = i; + while (node && index >= node.value.length) { + node = node.next; + index = 0; + } + return [node, index]; + }; + + while (p1 && p2) { + [p1, i1] = findNextPointerIndex(p1, i1 + 1); + [p2, i2] = findNextPointerIndex(p2, i2 + 1); + if ((p1 && p2 && p1.value[i1] !== p2.value[i2]) + || ((!p1 || !p2) && p1 !== p2)) return false; + } + return true; +} +// end::solution[] + +// tag::hasSameDataBrute1[] +function hasSameDataBrute1(l1, l2) { + function toString(node) { + const str = []; + for (let curr = node; curr; curr = curr.next) { + str.push(curr.value); + } + return str.join(''); + } + + // console.log({s1: toString(l1), s2: toString(l2) }); + return toString(l1) === toString(l2); +} +// end::hasSameDataBrute1[] + +function hasSameData1(l1, l2) { + let p1 = l1; + let p2 = l2; + + let i1 = 0; + let i2 = 0; + + while (p1 || p2) { + if (!p1 || !p2 || p1.value[i1] !== p2.value[i2]) return false; + + if (i1 < p1.value.length - 1) i1++; + else { + p1 = p1.next; + i1 = 0; + } + + if (i2 < p2.value.length - 1) i2++; + else { + p2 = p2.next; + i2 = 0; + } + } + return true; +} + +module.exports = { hasSameData, hasSameDataBrute1, hasSameData1 }; diff --git a/book/interview-questions/linkedlist-same-data.spec.js b/book/interview-questions/linkedlist-same-data.spec.js new file mode 100644 index 00000000..c0d26830 --- /dev/null +++ b/book/interview-questions/linkedlist-same-data.spec.js @@ -0,0 +1,41 @@ +const { hasSameData } = require('./linkedlist-same-data'); +const LinkedList = require('../../src/data-structures/linked-lists/linked-list'); + +describe('Linked List: has same data', () => { + it('should work with same data and shape', () => { + const l1 = new LinkedList(['hi']).first; + const l2 = new LinkedList(['hi']).first; + expect(hasSameData(l1, l2)).toEqual(true); + }); + + it('should work with different data', () => { + const l1 = new LinkedList(['ab']).first; + const l2 = new LinkedList(['a']).first; + expect(hasSameData(l1, l2)).toEqual(false); + }); + + it('should work with same data and but different shape', () => { + const l1 = new LinkedList(['h', 'e', 'l', 'l', 'o']).first; + const l2 = new LinkedList(['hello']).first; + expect(hasSameData(l1, l2)).toEqual(true); + }); + + it('should work with different data', () => { + const l1 = new LinkedList(['he', 'll', 'o']).first; + const l2 = new LinkedList(['ho', 'la']).first; + expect(hasSameData(l1, l2)).toEqual(false); + }); + + it('should handle empty', () => { + const l1 = new LinkedList(['hi']).first; + const l2 = new LinkedList(['', 'h', '', 'i']).first; + expect(hasSameData(l1, l2)).toEqual(true); + }); + + xit('should work with large data', () => { + const size = 1e6; // 1e7 takes 4sec. + const l1 = new LinkedList(Array(size).fill('x')).first; + const l2 = new LinkedList(Array(size).fill('z')).first; + expect(hasSameData(l1, l2)).toEqual(false); + }); +}); diff --git a/book/interview-questions/max-subarray.js b/book/interview-questions/max-subarray.js index 3684740a..0245702f 100644 --- a/book/interview-questions/max-subarray.js +++ b/book/interview-questions/max-subarray.js @@ -4,6 +4,7 @@ * @examples * maxSubArray([1, -3, 10, -5]); // => 10 * maxSubArray([-3,4,-1,2,1,-5]); // => 6 + * * @param {number[]} a - Array */ function maxSubArray(a) { diff --git a/book/interview-questions/merge-lists.js b/book/interview-questions/merge-lists.js new file mode 100644 index 00000000..ad26c326 --- /dev/null +++ b/book/interview-questions/merge-lists.js @@ -0,0 +1,37 @@ +const ListNode = require('../../src/data-structures/linked-lists/node'); +// tag::description[] +/** + * Given two sorted linked lists merge them while keeping the asc order. + * @examples + * mergeTwoLists([2,4,6], [1,3]); // => [1,2,3,4,6] + * mergeTwoLists([2,4,6], []); // => [2,4,6] + * mergeTwoLists([], [1,3]); // => [1,3] + * + * @param {ListNode} l1 - The root node of list 1 + * @param {ListNode} l2 - The root node of list 2 + */ +function mergeTwoLists(l1, l2) { + // end::description[] + // tag::solution[] + const sentinel = new ListNode(); + let p0 = sentinel; + let p1 = l1; + let p2 = l2; + + while (p1 || p2) { + if (!p1 || (p2 && p1.value > p2.value)) { + p0.next = p2; + p2 = p2.next; + } else { + p0.next = p1; + p1 = p1.next; + } + p0 = p0.next; + } + + return sentinel.next; +} +// end::solution[] + + +module.exports = { mergeTwoLists }; diff --git a/book/interview-questions/merge-lists.spec.js b/book/interview-questions/merge-lists.spec.js new file mode 100644 index 00000000..4b06e8ee --- /dev/null +++ b/book/interview-questions/merge-lists.spec.js @@ -0,0 +1,44 @@ +const { mergeTwoLists } = require('./merge-lists'); +const LinkedList = require('../../src/data-structures/linked-lists/linked-list'); + +describe('Linked List: Merge Lists', () => { + function asString(root) { + const values = []; + for (let curr = root; curr; curr = curr.next) { + values.push(curr.value); + } + return values.join(' -> '); + } + + it('should merge in asc order', () => { + const l1 = new LinkedList([2, 3, 4]).first; + const l2 = new LinkedList([1, 2]).first; + const actual = mergeTwoLists(l1, l2); + const expected = '1 -> 2 -> 2 -> 3 -> 4'; + expect(asString(actual)).toEqual(expected); + }); + + it('should handle empty list 1', () => { + const l1 = new LinkedList().first; + const l2 = new LinkedList([1, 2]).first; + const actual = mergeTwoLists(l1, l2); + const expected = '1 -> 2'; + expect(asString(actual)).toEqual(expected); + }); + + it('should handle empty list 1', () => { + const l1 = new LinkedList([2, 3, 4]).first; + const l2 = new LinkedList().first; + const actual = mergeTwoLists(l1, l2); + const expected = '2 -> 3 -> 4'; + expect(asString(actual)).toEqual(expected); + }); + + it('should handle empty lists', () => { + const l1 = new LinkedList().first; + const l2 = new LinkedList().first; + const actual = mergeTwoLists(l1, l2); + const expected = ''; + expect(asString(actual)).toEqual(expected); + }); +}); diff --git a/book/interview-questions/valid-parentheses.js b/book/interview-questions/valid-parentheses.js new file mode 100644 index 00000000..a822a214 --- /dev/null +++ b/book/interview-questions/valid-parentheses.js @@ -0,0 +1,27 @@ +// tag::description[] +/** + * Validate if the parentheses are opened and closed in the right order. + * + * @example + * isParenthesesValid('(){}[]'); // true + * isParenthesesValid('([{}])'); // true + * isParenthesesValid('([{)}]'); // false + * + * @param {string} string - The string + */ +function isParenthesesValid(string) { + // end::description[] + // tag::solution[] + const map = new Map([['(', ')'], ['{', '}'], ['[', ']']]); + const stack = []; + + for (const c of string) { + if (map.has(c)) stack.push(map.get(c)); + else if (c !== stack.pop()) return false; + } + + return stack.length === 0; +} +// end::solution[] + +module.exports = { isParenthesesValid }; diff --git a/book/interview-questions/valid-parentheses.spec.js b/book/interview-questions/valid-parentheses.spec.js new file mode 100644 index 00000000..fcb392f3 --- /dev/null +++ b/book/interview-questions/valid-parentheses.spec.js @@ -0,0 +1,23 @@ +const { isParenthesesValid } = require('./valid-parentheses'); + +describe('Stack: Valid Parentheses', () => { + it('should be valid', () => { + expect(isParenthesesValid('()')).toEqual(true); + }); + + it('should be valid with different kinds', () => { + expect(isParenthesesValid('()[]{}')).toEqual(true); + }); + + it('should be valid with different nested kinds', () => { + expect(isParenthesesValid('([{}])')).toEqual(true); + }); + + it('should not be valid if incomplete', () => { + expect(isParenthesesValid('()(')).toEqual(false); + }); + + it('should not be valid if invalid character is present', () => { + expect(isParenthesesValid('()-')).toEqual(false); + }); +});