Skip to content

Feat/set improvements #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ Use Linked Lists when:
#### [HashMaps](book/content/part03/map.asc)

Learn how to implement different types of Maps such as:
- [HashMap](book/content/part03/hashmap.asc)
- [TreeMap](book/content/part03/treemap.asc)
- [HashMap](book/content/part02/hash-map.asc)
- [TreeMap](book/content/part03/tree-map.asc)

Also, [learn the difference between the different Maps implementations](book/content/part03/time-complexity-graph-data-structures.asc):

Expand Down
5 changes: 2 additions & 3 deletions book/B-self-balancing-binary-search-trees.asc
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ Let's go one by one.

Right rotation moves a node on the right as a child of another node.

Take a look at the `@example` in the code below.
As you can see we have an unbalanced tree `4-3-2-1`.
Take a look at the examples in the code in the next section.
As you will see we have an unbalanced tree `4-3-2-1`.
We want to balance the tree, for that we need to do a right rotation of node 3.
So, we move node 3 as the right child of the previous child.

Expand Down Expand Up @@ -140,4 +140,3 @@ This rotation is also referred to as `RL rotation`.
=== Self-balancing trees implementations

So far, we have study how to make tree rotations which are the basis for self-balancing trees. There are different implementations of self-balancing trees such a Red-Black Tree and AVL Tree.

8 changes: 4 additions & 4 deletions book/D-interview-questions-solutions.asc
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ The complexity of any of the BFS methods or DFS is similar.

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

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

Expand Down Expand Up @@ -482,7 +482,7 @@ include::interview-questions/two-sum.js[tags=description;solution]

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

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

Expand Down Expand Up @@ -590,7 +590,7 @@ The sum is 1, however `sum - k` is `0`. If it doesn't exist on the map, we will


[#set-q-most-common-word]
include::content/part03/set.asc[tag=set-q-most-common-word]
include::content/part02/hash-set.asc[tag=set-q-most-common-word]

This problem requires multiple steps. We can use a `Set` for quickly looking up banned words. For getting the count of each word, we used a `Map`.

Expand Down Expand Up @@ -632,7 +632,7 @@ include::interview-questions/most-common-word.js[tags=explicit]


[#set-q-longest-substring-without-repeating-characters]
include::content/part03/set.asc[tag=set-q-longest-substring-without-repeating-characters]
include::content/part02/hash-set.asc[tag=set-q-longest-substring-without-repeating-characters]

One of the most efficient ways to find repeating characters is using a `Map` or `Set`. Use a `Map` when you need to keep track of the count/index (e.g., string -> count) and use a `Set` when you only need to know if there are repeated characters or not.

Expand Down
2 changes: 1 addition & 1 deletion book/content/colophon.asc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ For online information and ordering this and other books, please visit https://a

No part of this publication may be produced, store in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without the prior written permission of the publisher.

While every precaution has been taking in the preparation of this book, the publisher and author assume no responsibility for errors or omissions, or damages resulting from the use of the information contained herein.
While every precaution has been taking in the preparation of this book, the publisher and author assume no responsibility for errors or omissions, or damages resulting from using the information contained herein.

// {revremark}, {revdate}.
Version {revnumber}, {revdate}.
2 changes: 1 addition & 1 deletion book/content/dedication.asc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[dedication]
== Dedication

_To my wife Nathalie who supported me in my long hours of writing and my baby girl Abigail._
_To my wife Nathalie, who supported me in my long hours of writing, and my baby girl Abigail._
4 changes: 2 additions & 2 deletions book/content/introduction.asc
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
You are about to become a better programmer and grasp the fundamentals of Algorithms and Data Structures.
Let's take a moment to explain how we are going to do that.

This book is divided into 4 main parts:
This book is divided into four main parts:

In *Part 1*, we will cover the framework to compare and analyze algorithms: Big O notation. When you have multiple solutions to a problem, this framework comes handy to know which solution will scale better.
In *Part 1*, we will cover the framework to compare and analyze algorithms: Big O notation. When you have multiple solutions to a problem, this framework comes in handy to know which solution will scale better.

In *Part 2*, we will go over linear data structures and trade-offs about using one over another.
After reading this part, you will know how to trade space for speed using Maps, when to use a linked list over an array, or what problems can be solved using a stack over a queue.
Expand Down
4 changes: 2 additions & 2 deletions book/content/part01/algorithms-analysis.asc
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ _7n^3^ + 3n^2^ + 5_

You can express it in Big O notation as _O(n^3^)_. The other terms (_3n^2^ + 5_) will become less significant as the input grows bigger.

Big O notation only cares about the “biggest” terms in the time/space complexity. It combines what we learn about time and space complexity, asymptotic analysis, and adds a worst-case scenario.
Big O notation only cares about the “biggest” terms in the time/space complexity. It combines what we learn about time and space complexity, asymptotic analysis and adds a worst-case scenario.

.All algorithms have three scenarios:
* Best-case scenario: the most favorable input arrangement where the program will take the least amount of operations to complete. E.g., a sorted array is beneficial for some sorting algorithms.
Expand All @@ -152,7 +152,7 @@ Big O notation only cares about the “biggest” terms in the time/space comple

To sum up:

TIP: Big O only cares about the run time function's highest order on the worst-case scenario.
TIP: Big O only cares about the run time function's highest order in the worst-case scenario.

WARNING: Don't drop terms that are multiplying other terms. _O(n log n)_ is not equivalent to _O(n)_. However, _O(n + log n)_ is.

Expand Down
10 changes: 6 additions & 4 deletions book/content/part01/big-o-examples.asc
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ Before we dive in, here’s a plot with all of them.

.CPU operations vs. Algorithm runtime as the input size grows
// image::image5.png[CPU time needed vs. Algorithm runtime as the input size increases]
image::big-o-running-time-complexity.png[CPU time needed vs. Algorithm runtime as the input size increases]
image::time-complexity-manual.png[{half-size}]

The above chart shows how the algorithm's running time is related to the work the CPU has to perform. As you can see, O(1) and O(log n) is very scalable. However, O(n^2^) and worst can convert your CPU into a furnace 🔥 for massive inputs.
The above chart shows how the algorithm's running time is related to the CPU's work. As you can see, O(1) and O(log n) is very scalable. However, O(n^2^) and worst can convert your CPU into a furnace 🔥 for massive inputs.

[[constant]]
==== Constant
Expand Down Expand Up @@ -71,7 +71,9 @@ include::{codedir}/runtimes/02-binary-search.js[tag=binarySearchRecursive]

This binary search implementation is a recursive algorithm, which means that the function `binarySearchRecursive` calls itself multiple times until the program finds a solution. The binary search splits the array in half every time.

Finding the runtime of recursive algorithms is not very obvious sometimes. It requires some tools like recursion trees or the https://adrianmejia.com/blog/2018/04/24/analysis-of-recursive-algorithms/[Master Theorem]. The `binarySearch` divides the input in half each time. As a rule of thumb, when you have an algorithm that divides the data in half on each call, you are most likely in front of a logarithmic runtime: _O(log n)_.
Finding the runtime of recursive algorithms is not very obvious sometimes. It requires some approaches like recursion trees or the https://adrianmejia.com/blog/2018/04/24/analysis-of-recursive-algorithms/[Master Theorem].

Since the `binarySearch` divides the input in half each time. As a rule of thumb, when you have an algorithm that divides the data in half on each call, you are most likely in front of a logarithmic runtime: _O(log n)_.

[[linear]]
==== Linear
Expand Down Expand Up @@ -171,7 +173,7 @@ Cubic *O(n^3^)* and higher polynomial functions usually involve many nested loop
[[cubic-example]]
===== 3 Sum

Let's say you want to find 3 items in an array that add up to a target number. One brute force solution would be to visit every possible combination of 3 elements and add them up to see if they are equal to target.
Let's say you want to find 3 items in an array that add up to a target number. One brute force solution would be to visit every possible combination of 3 elements and add them to see if they are equal to the target.

[source, javascript]
----
Expand Down
6 changes: 3 additions & 3 deletions book/content/part01/how-to-big-o.asc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ endif::[]
=== How to determine time complexity from code?

In general, you can determine the time complexity by analyzing the program's statements.
However, you have to be mindful how are the statements arranged. Suppose they are inside a loop or have function calls or even recursion. All these factors affect the runtime of your code. Let's see how to deal with these cases.
However, you have to be mindful of how are the statements arranged. Suppose they are inside a loop or have function calls or even recursion. All these factors affect the runtime of your code. Let's see how to deal with these cases.

*Sequential Statements*

Expand Down Expand Up @@ -114,7 +114,7 @@ If instead of `m`, you had to iterate on `n` again, then it would be `O(n^2)`. A
[[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.
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, you might infer it from the language/library documentation if you use a 3rd party function.

Let's say you have the following program:

Expand Down Expand Up @@ -210,7 +210,7 @@ graph G {

If you take a look at the generated tree calls, the leftmost nodes go down in descending order: `fn(4)`, `fn(3)`, `fn(2)`, `fn(1)`, which means that the height of the tree (or the number of levels) on the tree will be `n`.

The total number of calls, in a complete binary tree, is `2^n - 1`. As you can see in `fn(4)`, the tree is not complete. The last level will only have two nodes, `fn(1)` and `fn(0)`, while a complete tree would have 8 nodes. But still, we can say the runtime would be exponential `O(2^n)`. It won't get any worst because `2^n` is the upper bound.
The total number of calls in a complete binary tree is `2^n - 1`. As you can see in `fn(4)`, the tree is not complete. The last level will only have two nodes, `fn(1)` and `fn(0)`, while a full tree would have eight nodes. But still, we can say the runtime would be exponential `O(2^n)`. It won't get any worst because `2^n` is the upper bound.

==== Summary

Expand Down
4 changes: 2 additions & 2 deletions book/content/part02/array-vs-list-vs-queue-vs-stack.asc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ endif::[]

=== Array vs. Linked List & Queue vs. Stack

In this part of the book, we explored the most used linear data structures such as Arrays, Linked Lists, Stacks and Queues. We implemented them and discussed the runtime of their operations.
In this part of the book, we explored the most used linear data structures such as Arrays, Linked Lists, Stacks, and Queues. We implemented them and discussed the runtime of their operations.

.Use Arrays when…
* You need to access data in random order fast (using an index).
Expand All @@ -17,7 +17,7 @@ In this part of the book, we explored the most used linear data structures such
* You want constant time to remove/add from extremes of the list.

.Use a Queue when:
* You need to access your data on a first-come, first served basis (FIFO).
* You need to access your data on a first-come, first-served basis (FIFO).
* You need to implement a <<part03-graph-data-structures#bfs-tree, Breadth-First Search>>

.Use a Stack when:
Expand Down
12 changes: 6 additions & 6 deletions book/content/part02/array.asc
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ array.pop(); // ↪️111
// array: [2, 5, 1, 9]
----

No other element was touched, so it’s an _O(1)_ runtime.
While deleting the last element, no other item was touched, so that’s an _O(1)_ runtime.

.JavaScript built-in `array.pop`
****
Expand Down Expand Up @@ -293,7 +293,7 @@ To sum up, the time complexity of an array is:
| `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.
| `slice` ^| O(n) | Returns a shallow copy of the array.
|===
//end::table

Expand Down Expand Up @@ -474,7 +474,7 @@ Notice that many middle branches (in red color) have the same numbers, but in a

*Sliding window algorithm*

Another approach is using sliding windows. Since the sum always has `k` elements, we can compute the cumulative sum for k first elements from the left. Then, we slide the "window" to the right and remove one from the left until we cover all the right items. In the end, we would have all the possible combinations without duplicated work.
Another approach is using sliding windows. Since the sum always has `k` elements, we can compute the cumulative sum for the k first elements from the left. Then, we slide the "window" to the right and remove one from the left until we cover all the right items. In the end, we would have all the possible combinations without duplicated work.

Check out the following illustration:

Expand Down Expand Up @@ -522,7 +522,7 @@ maxSubArray([-3, 4,-1, 2, 1, -5]); // 6 (sum [4,-1, 2, 1])
maxSubArray([-2, 1, -3, 4, -1, 3, 1]); // 7 (sum [4,-1, 3, 1])
----

// _Seen in interviews at: Amazon, Apple, Google, Microsoft, Facebook_
_Common in interviews at: Amazon, Apple, Google, Microsoft, Facebook_
// end::array-q-max-subarray[]

[source, javascript]
Expand All @@ -537,7 +537,7 @@ _Solution: <<array-q-max-subarray>>_
// tag::array-q-buy-sell-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 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)_
*AR-2*) _You have 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:

Expand All @@ -548,7 +548,7 @@ 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_
_Common in interviews at: Amazon, Facebook, Bloomberg_
// end::array-q-buy-sell-stock[]

[source, javascript]
Expand Down
19 changes: 10 additions & 9 deletions book/content/part02/hash-map.asc
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ endif::[]

(((Map))) (((HashMap))) (((HashTable))) (((Data Structures, Linear, HashMap)))
[[hashmap-chap]]
=== Hash Map
=== Map

A Map is a data structure where a `key` is mapped to a `value`. It's used for a fast lookup of values based on the given key. Only one key can map to a value (no duplicates).
A Map is a data structure where a `key` is mapped to a `value`. It's used for a fast lookup of values based on the given key. Only one key can map to a value (no key duplicates are possible).

NOTE: Map has many terms depending on the programming language. Here are some other names: Hash Map, Hash Table, Associative Array, Unordered Map, Dictionary.

Expand Down Expand Up @@ -35,6 +35,7 @@ A Map uses an array internally. It translates the key into an array's index usin

JavaScript has two ways to use Maps: one uses objects (`{}`), and the other is using the built-in `Map`.

[[hashmap-examples]]
.Using Objects as a HashMap.
[source, javascript]
----
Expand Down Expand Up @@ -241,7 +242,7 @@ map.set('art', 8);
.Internal HashMap representation
image::image41.png[image,width=528,height=299]

No hash function is perfect, so it's going to map two different keys to the same value for some cases. That's what we called a *collision*. When that happens, we chain the results on the same bucket. If we have too many collisions, it could degrade the lookup time from `O(1)` to `O(n)`.
No hash function is perfect, so it will map two different keys to the same value for some cases. That's what we called a *collision*. When that happens, we chain the results on the same bucket. If we have too many collisions, it could degrade the lookup time from `O(1)` to `O(n)`.

The Map doubles the size of its internal array to minimize collisions when it reaches a certain threshold. This restructuring is called a *rehash*. This *rehash* operation takes `O(n)`, since we have to visit every old key/value pair and remap it to the new internal array. Rehash doesn't happen very often, so statistically speaking, Maps can insert/read/search in constant time `O(1)`.

Expand Down Expand Up @@ -342,7 +343,7 @@ The LRU cache behavior is almost identical to the Map.
- LRU cache has a limited size, while Map grows until you run out of memory.
- LRU cache removes the least used items once the limit is reached.

We can extend the Map functionality. Also, the Map implementation on JavaScript already keeps the items by insertion order. So, every time we read or update a value, we can remove it from where it was and add it back. That way, the oldest (least used) it's the first element on the Map.
We can extend the Map functionality. Also, the Map implementation on JavaScript already keeps the items by insertion order. Every time we read or update a value, we can remove it from where it was and add it back. That way, the oldest (least used) it's the first element on the Map.

.Solution: extending Map
[source, javascript]
Expand Down Expand Up @@ -504,9 +505,9 @@ image:sliding-window-map.png[sliding window for abbadvdf]

As you can see, we calculate the length of the string on each iteration and keep track of the maximum value.

What would this look like in code? Let's try a couple of solutions. Let's go first with the brute force and then improve.
What would this look like in code? Let's try a couple of solutions. Let's go first with the brute force and then how we can improve it.

We can have two pointers, `lo` and `hi` to define a window. We can can use two for-loops for that. Later, within `lo` to `hi` we want to know if there's a duplicate value. We can use two other for-loops to check for duplicates (4 nested for-loop)! To top it off, we are using labeled breaks to skip updating the max if there's a duplicate.
We can have two pointers, `lo` and `hi`, to define a window. We can use two for-loops for that. Later, within `lo` to `hi` window, we want to know if there's a duplicate value. A simple and naive approach is to use another two for-loops to check for duplicates (4 nested for-loop)! We need labeled breaks to skip updating the max if there's a duplicate.

WARNING: The following code can hurt your eyes. Don't try this in production; for better solutions, keep reading.

Expand Down Expand Up @@ -614,7 +615,7 @@ Something that might look unnecessary is the `Math.max` when updating the `lo` p

.Complexity Analysis
- Time Complexity: `O(n)`. We do one pass and visit each character once.
- Space complexity: `O(n)`. We store everything one the Map so that the max size would be `n`.
- Space complexity: `O(n)`. We store everything on the Map so that the max size would be `n`.

<<<
==== Practice Questions (((Interview Questions, Hash Map)))
Expand All @@ -626,7 +627,7 @@ Something that might look unnecessary is the `Math.max` when updating the `lo` p

// end::hashmap-q-two-sum[]

// _Seen in interviews at: Amazon, Google, Apple._
_Common in interviews at: Amazon, Google, Apple._

Examples:

Expand Down Expand Up @@ -655,7 +656,7 @@ _Solution: <<hashmap-q-two-sum>>_

// end::hashmap-q-subarray-sum-equals-k[]

// _Seen in interviews at: Facebook, Google, Amazon_
_Common in interviews at: Facebook, Google, Amazon_

Examples:

Expand Down
Loading