diff --git a/book/content/part01/algorithms-analysis.asc b/book/content/part01/algorithms-analysis.asc index d06b0a4f..dc7b6893 100644 --- a/book/content/part01/algorithms-analysis.asc +++ b/book/content/part01/algorithms-analysis.asc @@ -28,15 +28,14 @@ Before going deeper into space and time complexity, let's cover the basics real Algorithms (as you might know) are steps of how to do some tasks. When you cook, you follow a recipe (or an algorithm) to prepare a dish. Let's say you want to make a pizza. -.Example of an algorithm +.Example of an algorithm to make pizza [source, javascript] ---- -import { punchDown, rollOut, applyToppings, Oven } from '../pizza-utils'; +import { rollOut, applyToppings, Oven } from '../pizza-utils'; function makePizza(dough, toppings = ['cheese']) { const oven = new Oven(450); - const punchedDough = punchDown(dough); - const rolledDough = rollOut(punchedDough); + const rolledDough = rollOut(dough); const rawPizza = applyToppings(rolledDough, toppings); const pizzaPromise = oven.bake(rawPizza, { minutes: 20 }); return pizzaPromise; diff --git a/book/content/part01/how-to-big-o.asc b/book/content/part01/how-to-big-o.asc index 3067704f..951cee6b 100644 --- a/book/content/part01/how-to-big-o.asc +++ b/book/content/part01/how-to-big-o.asc @@ -111,6 +111,7 @@ T(n) = n * [t(statement 1) + m * t(statement 2...3)] Assuming the statements from 1 to 3 are `O(1)`, we would have a runtime of `O(n * m)`. If instead of `m`, you had to iterate on `n` again, then it would be `O(n^2)`. Another typical case is having a function inside a loop. Let's see how to deal with that next. +[[big-o-function-statement]] *Function call statements* When you calculate your programs' time complexity and invoke a function, you need to be aware of its runtime. If you created the function, that might be a simple inspection of the implementation. However, if you are using a library function, you might infer it from the language/library documentation. diff --git a/book/content/part02/array.asc b/book/content/part02/array.asc index 2e5aea39..a5b9e567 100644 --- a/book/content/part02/array.asc +++ b/book/content/part02/array.asc @@ -7,79 +7,97 @@ 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 already. But, are you aware of the runtimes of `push`, `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. - -.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. 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. -**** +TIP: Strings are a collection of characters. Most of the array methods apply to strings as well. Arrays look like this: -.Array representation: each value is accessed through an index. +.Array representation: You can access each value in constant time through its 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 at the different operations that we can do with arrays. +===== Read and Update -==== Insertion +Arrays are a contiguous collection of elements that can be accessed randomly using an index. This access by index operation takes `O(1)` time. Let’s take a look at the different functions that we can do with arrays. -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 +.Reading elements from an array and string [source, javascript] ---- -// (1) Add elements at the creation time: const array = [2, 5, 1, 9, 6, 7]; - -// (2) initialize an empty array and add values later -const array2 = []; -array2[3] = 1; -array2[100] = 2; -array2 // [empty × 3, 1, empty × 96, 2] +const string = "hello"; +console.log(array[2]); // 1 +console.log(string[1]); // "e" ---- -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. +As you can see, you can access the string's characters using the same operator as arrays. + +You can update arrays in the same way, using the `[]` operator. However, you can't modify strings. They are immutable! +.Reading elements from an array and string [source, javascript] ---- -console.log(array2.length); // 101 -console.log(array2); // [empty × 3, 1, empty × 96, 2] +const array = [2, 5, 1, 9, 6, 7]; +const string = "hello"; +array[2] = 117; +console.log(array[2]); // 117 +string[1] = "z"; // doesn't change the string. +console.log(string[1]); // "e" ---- +WARNING: When you try to modify and string, you won't get an error or anything. It just gets ignored! Your only option is to create a new string with the adjusted value. + +===== Insertion -The runtime for inserting elements using an index is always is constant: _O(1)_. +Insertions on an array have different times complexities. +O(1): constant time (on average) to append a value at the end of the array. +O(n): linear time to insert a value at the beginning or middle. -===== Inserting at the beginning of the array +====== Inserting at the beginning of the array -What if you want to insert a new element at the beginning of the array? You would have to push every item to the right. +What if you want to insert a new element at the beginning of the array? You would have to push every item to the right. We can use the following method: + +.Syntax +[source, javascript] +---- +const newArrLength = arr.unshift(element1[, ...[, elementN]]); +---- + +Here's an example: .Insert to head [source, javascript] ---- -const array = [2, 5, 1, 9, 6, 7]; +const array = [2, 5, 1]; array.unshift(0); // ↪️ 8 -// array: [0, 2, 5, 1, 9, 6, 7] +console.log(array); // [ 0, 2, 5, 1 ] +array.unshift(-2, -1); // ↪️ 6 +console.log(array); // [ -2, -1, 0, 2, 5, 1 ] ---- -As you can see, `2` was at index 0, now was pushed to index 1, and everything else is on a different index. `unshift` takes *O(n)* since it affects all the elements in the array. +As you can see, `2` was at index 0, now was pushed to index 1, and everything else is on a different index. `unshift` takes *O(n)* since it affects **all** the elements of the array. .JavaScript built-in `array.unshift` **** -The `unshift()` method adds one or more elements to the beginning of an array and returns the array's new length. +The `unshift()` method adds one or more elements to the beginning of an array and returns its new length. -Runtime: O(n). +Runtime: `O(n)`. **** -===== Inserting at the middle of the array +====== Inserting at the middle of the array + +Inserting a new element in the middle involves moving part of the array but not all of the items. We can use `splice` for that: -Inserting a new element in the middle involves moving part of the array but not all of the items. +.Syntax +[source, javascript] +---- +const arrDeletedItems = arr.splice(start[, deleteCount[, item1[, item2[, ...]]]]); +---- + +Based on the parameters it takes, you can see that we can add and delete items. Here's an example of inserting in the middle. .Inserting element in the middle [source, javascript] @@ -94,12 +112,20 @@ The Big O for this operation would be *O(n)* since, in the worst case, it would .JavaScript built-in `array.splice` **** -The `splice()` method changes an array's contents by removing existing elements or adding new elements. Splice returns an array containing the deleted items. +The `splice()` method changes an array's contents by removing existing elements or adding new items. Splice returns an array containing the deleted items. Runtime: O(n). **** -===== Inserting at the end of the array +====== Inserting at the end of the array + +For inserting items at the end of the array, we can use: push. + +.Syntax +[source, javascript] +---- +const newArrLength = arr.push([element1[, ...[, elementN]]]); +---- We can push new values to the end of the array like this: @@ -116,15 +142,15 @@ 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 array's new length. +The `push()` method adds one or more elements to the end of an array and returns its new length. Runtime: O(1). **** [[array-search-by-value]] -==== Searching by value and index +===== Searching by value and index -Searching by the index is very easy using the `[]` operator: +As we saw before, searching by the index is very easy using the `[]` operator: .Search by index [source, javascript] @@ -133,75 +159,56 @@ const array = [2, 5, 1, 9, 6, 7]; array[4]; // ↪️ 6 ---- -Searching by index takes constant time - *O(1)* - to retrieve values out of the array. If we want to get fancier, we can create a function: +Searching by index takes constant time - *O(1)* - to retrieve values out of the array. -// image:image17.png[image,width=528,height=293] +Searching by value can be done using `indexOf`. -.Search by index +.Syntax [source, javascript] ---- -/** - * Search for array's element by index - * - * @example Given array = [2, 5, 1, 9, 6, 7, -1]; - * searchByIndex(array, 3); //↪️ 9 - * searchByIndex(array, 6); //↪️ -1 - * searchByIndex(array, 13); //↪️ undefined - * @param {array} array - * @param {number} index - * @returns {any} value or undefined if not found - */ -function searchByIndex(array, index) { - return array[index]; -} +const index = arr.indexOf(searchElement[, fromIndex]); ---- -Finding out if a value is in the array or not is a different story. - -// image:image18.png[image,width=528,height=338] +If the value is there, we will get the index, otherwise `-1`. .Search by value [source, javascript] ---- -/** - * Search for array's element by value - * - * @example Given array = [2, 5, 1, 9, 6, 7]; - * searchByValue(array, 9); //↪️ 3 - * searchByValue(array, 13); //↪️ -1 - * @param {array} array - * @param {any} value - */ -function searchByValue(array, value) { - for (let index = 0; index < array.length; index++) { - const element = array[index]; - if (element === value) return index; - } - return -1; -} +const array = [2, 5, 1, 9, 6, 7]; +console.log(array.indexOf(9)); // ↪️ 3 +console.log(array.indexOf(90)); // ↪️ -1 ---- -We would have to loop through the whole array (worst case) or until we find it: *O(n)*. +Internally, `indexOf` has to loop through the whole array (worst case) or until we find the first occurrence. Time complexity is *O(n)*. -==== Deletion +===== Deletion There are three possible deletion scenarios (similar to insertion): removing at the beginning, middle, or end. -===== Deleting element from the beginning +====== Deleting element from the beginning + +Deleting from the beginning can be done using the `splice` function and the `shift`. For simplicity, we will use the latter. -Deleting from the beginning can be done using the `splice` function and also the `shift`. For simplicity, we will use the latter. +.Syntax +[source, javascript] +---- +const removedElement = arr.shift(); +let arrDeletedItems = arr.splice(start[, deleteCount[, item1[, item2[, ...]]]]); +---- .Deleting from the beginning of the array. [source, javascript] ---- const array = [2, 111, 5, 1, 9, 6, 7]; // Deleting from the beginning of the array. -array.shift(); // ↪️2 -array.shift(); // ↪️111 -// array: [5, 1, 9, 6, 7] +array.shift(); // ↪️ 2 +array.shift(); // ↪️ 111 +console.log(array); // [5, 1, 9, 6, 7] +array.splice(0, 1); // ↪️ [ 5 ] +console.log(array); // [ 1, 9, 6, 7 ] ---- -As expected, this will change every index, so this takes *O(n)*. +As expected, this will change every index on the array, so this takes linear time: *O(n)*. .JavaScript built-in array.shift **** @@ -210,25 +217,35 @@ The `shift()` method shift all elements to the left. In turn, it removes the fir Runtime: O(n). **** -===== Deleting element from the middle +====== Deleting element from the middle We can use the `splice` method for deleting an item from the middle of an array. +You can delete multiple items at once: + .Deleting from the middle [source, javascript] ---- const array = [0, 1, 2, 3, 4]; // Deleting from the middle -array.splice(2, 1); // ↪️[2] <1> -// array: [0, 1, 3, 4] +array.splice(2, 3); // ↪️ [ 2, 3, 4 ] <1> +console.log(array); // [0, 1] ---- -<1> delete 1 element at position 2 +<1> delete 3 elements starting on position 2 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 +====== Deleting element from the end + +Removing the last element is very straightforward using pop: -Removing the last element is very straightforward: +.Syntax +[source, javascript] +---- +const removedItem = arr.pop(); +---- + +Here's an example: .Deleting last element from the array [source, javascript] @@ -238,7 +255,7 @@ array.pop(); // ↪️111 // array: [2, 5, 1, 9] ---- -No other element has been shifted, so it’s an _O(1)_ runtime. +No other element was touched, so it’s an _O(1)_ runtime. .JavaScript built-in `array.pop` **** @@ -247,7 +264,7 @@ The `pop()` method removes the last element from an array and returns that eleme Runtime: O(1). **** -==== Array Complexity +===== Array Complexity To sum up, the time complexity of an array is: (((Tables, Linear DS, Array Complexities))) @@ -268,11 +285,14 @@ To sum up, the time complexity of an array is: .Array Operations time complexity |=== | Operation | Time Complexity | Usage -| push ^| O(1) | Insert element to the right side. -| pop ^| O(1) | Remove the rightmost element. -| unshift ^| O(n) | Insert element to the left side. -| shift ^| O(n) | Remove leftmost element. -| splice ^| O(n) | Insert and remove from anywhere. +| `push` ^| O(1) | Insert element on the right side. +| `pop` ^| O(1) | Remove the rightmost element. +| `[]` ^| O(1) | Search for element by index. +| `indexOf` ^| O(n) | Search for element by value. +| `unshift` ^| O(n) | Insert element on the left side. +| `shift` ^| O(n) | Remove leftmost element. +| `splice` ^| O(n) | Insert and remove from anywhere. +| `slice` ^| O(n) | Returns shallow copy of the array. |=== //end::table @@ -306,9 +326,9 @@ include::../../interview-questions/max-subarray.js[tag=description] _Solution: <>_ // tag::array-q-buy-sell-stock[] -===== Best Time to Buy and Sell an Stock +===== Best Time to Buy and Sell a Stock -*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)_ +*AR-2*) _You are given an array of integers. Each value represents the closing value of the stock on that day. You have only one chance to buy and then sell. What's the maximum profit you can obtain? (Note: you have to buy first and then sell)_ Examples: @@ -317,7 +337,6 @@ Examples: maxProfit([1, 2, 3]) // 2 (buying at 1 and selling at 3) maxProfit([3, 2, 1]) // 2 (no buys) maxProfit([5, 10, 5, 10]) // 5 (buying at 5 and selling at 10) - ---- // _Seen in interviews at: Amazon, Facebook, Bloomberg_ diff --git a/book/images/big-o-recursive-example.png b/book/images/big-o-recursive-example.png index 67395f3c..77aae598 100644 Binary files a/book/images/big-o-recursive-example.png and b/book/images/big-o-recursive-example.png differ diff --git a/book/images/recursive-fibonacci-call-tree.png b/book/images/recursive-fibonacci-call-tree.png index f6e028c3..ff7b31f3 100644 Binary files a/book/images/recursive-fibonacci-call-tree.png and b/book/images/recursive-fibonacci-call-tree.png differ diff --git a/book/images/time-complexity-examples.png b/book/images/time-complexity-examples.png new file mode 100644 index 00000000..6ba63230 Binary files /dev/null and b/book/images/time-complexity-examples.png differ diff --git a/book/part02-linear-data-structures.asc b/book/part02-linear-data-structures.asc index c357fb1d..6b3ddae2 100644 --- a/book/part02-linear-data-structures.asc +++ b/book/part02-linear-data-structures.asc @@ -1,9 +1,11 @@ [[part02-linear-data-structures]] == Linear Data Structures -Data Structures comes in many flavors. There’s no one to rule them all. You have to know the tradeoffs so you can choose the right one for the job. +Data Structures come in many flavors. There's no one to rule them all. You have to know the tradeoffs so you can choose the right one for the job. -Even though in your day-to-day, you might not need to re-implementing them, knowing how they work internally would help you know when to use one over the other or even tweak them to create a new one. We are going to explore the most common data structures' time and space complexity. +In your day-to-day work, you might not need to re-implement basic data structures. However, knowing how they work internally can help you understand their time complexity better (Remember the chapter <>). + +When you are aware of the data structures implementations, you spot when to use one over the other or even extend them to create a new one. We are going to explore the most common data structures' time and space complexity. .In this part we are going to learn about the following linear data structures: - <> @@ -22,6 +24,7 @@ If you want to have a general overview of each one, take a look at the following +++ endif::[] +<<< include::content/part02/array.asc[] <<< @@ -35,5 +38,3 @@ include::content/part02/queue.asc[] <<< include::content/part02/array-vs-list-vs-queue-vs-stack.asc[] - -