├── .gitattributes ├── .gitignore ├── 01-introduction-to-algorithms ├── binarySearch.js ├── binarySearch.test.js └── exercises.md ├── 02-selection-sort ├── exercises.md ├── selectionSort.js └── selectionSort.test.js ├── 03-recursion ├── exercises.md └── stack.png ├── 04-quicksort ├── binarySearchRecursive.js ├── binarySearchRecursive.test.js ├── countRecursive.js ├── countRecursive.test.js ├── exercises.md ├── maxRecursive.js ├── maxRecursive.test.js ├── quicksort.js ├── quicksort.test.js ├── sumRecursive.js └── sumRecursive.test.js ├── 05-hash-tables └── exercises.md ├── 06-breadth-first-search ├── breadthFirstSearch.js ├── breadthFirstSearch.test.js ├── exercise-6.5.png ├── exercises.md ├── graph-6.1.png ├── graph-6.2.png ├── graph-6.3.png ├── graph-6.4.png ├── names.json ├── race.json └── words.json ├── 07-dijkstras-algorithm ├── dijkstrasAlgorithm.js ├── dijkstrasAlgorithm.test.js ├── exercise-7.1.png ├── exercises.md ├── graph-a.json ├── graph-b.json └── graph.json ├── 08-greedy-algorithms ├── exercises.md ├── setCoverage.js ├── setCoverage.test.js ├── states.json └── stations.json ├── 09-dynamic-programming └── exercises.md ├── 10-k-nearest-neighbors └── exercises.md ├── LICENSE ├── README.md ├── jasmine.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Yarn Integrity file 47 | .yarn-integrity 48 | 49 | # Custom 50 | .eslintrc 51 | -------------------------------------------------------------------------------- /01-introduction-to-algorithms/binarySearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A binary search algorithm. 3 | * @param {Array} array The array to search. The array should be sorted before calling this function (typically using the same compare function). 4 | * @param {Any} item The item to search for in the array 5 | * @param {Function} [compare] A sorting function to use to sort the array (should return 1, 0, or -1) 6 | * @return {Integer|null} Returns the index of the item in the array if found, and null if not 7 | */ 8 | const binarySearch = (array, item, compare = (a, b) => a - b) => { 9 | 10 | let comparison; 11 | let mid; 12 | let low = 0; 13 | let high = array.length - 1; 14 | 15 | while (low <= high) { // while you haven't narrowed it down to one element 16 | 17 | mid = Math.round(low + ((high - low) / 2)); // divide by 2 while avoiding overflow 18 | comparison = Number(compare(array[mid], item)); // faster to reassign than redeclare comparison 19 | 20 | if (comparison < 0) low = mid + 1; // too low 21 | else if (comparison > 0) high = mid - 1; // too high 22 | else return mid; // item found 23 | 24 | } 25 | 26 | return null; // no result found 27 | 28 | }; 29 | 30 | module.exports = binarySearch; 31 | -------------------------------------------------------------------------------- /01-introduction-to-algorithms/binarySearch.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | no-console, 4 | prefer-arrow-callback, 5 | */ 6 | 7 | const binarySearch = require('./binarySearch'); 8 | 9 | it(`binarySearch`, function() { 10 | 11 | const len = 1e4; 12 | const array = Array.from(Array(len), (item, i) => i + 1); 13 | const num = Math.floor(Math.random() * len); 14 | 15 | console.time(`\nbinarySearch`); 16 | const result = binarySearch(array, num); 17 | console.timeEnd(`\nbinarySearch`); 18 | expect(result).toBe(num - 1); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /01-introduction-to-algorithms/exercises.md: -------------------------------------------------------------------------------- 1 | 1. Suppose you have a list of 128 names, and you're searching through it using binary search. What's the maximum number of steps it would take? 2 | 3 | > The maximum number of steps it would take to find a name in a list of 128 names would be `log_2(128)` or `7` steps: 4 | 5 | > ``` 6 | log_2(128) = x 7 | 2^x = 128 8 | 2^x = 2^7 9 | x = 7 10 | ``` 11 | 12 | 2. Suppose you double the size of the list. What's the maximum number of steps now? 13 | 14 | > If you double the size of the list to 256 name, the maximum number of steps it would take to find a name in the list would be `log_2(256)` or `8`. 15 | 16 | >``` 17 | > log_2(256) = x 18 | > 2^x = 128 19 | > 2^x = 2^8 20 | > x = 8 21 | ``` 22 | 23 | Give the run time for each of the following scenarios in terms of Big O: 24 | 25 | 3. You have a name, and you want to find the person's phone number in the phone book. 26 | 27 | > Because the phone book is sorted alphabetically, you can use a binary search algorithm, which would have a run time of `O(log_2(n))`. 28 | 29 | 4. You have a phone number, and you want to find the person's name in the phone book. (Hint: You'll have to search through the whole book!) 30 | 31 | > Because the phone book is not sorted by phone number, you will have to use a linear search algorithm, which would have a run time of `O(n)`. 32 | 33 | 5. You want to read the numbers of every person in the phone book. 34 | 35 | > This would also require a linear search algorithm, and have a run time of `O(n)`. 36 | 37 | 6. You want to read the numbers of just the *A*'s. (This is a tricky one! It involves concepts that are covered more in chapter 4. Read the answer - you may be surprised!) 38 | 39 | > Even though you only need to read `1/26` of the names in the phone book, the growth rate of the number of operations is still linear. `f(x) = x/26` and `f(x) = x` are both linear equations. Therefore the run time of this search would still be `O(n)`. 40 | -------------------------------------------------------------------------------- /02-selection-sort/exercises.md: -------------------------------------------------------------------------------- 1 | 1. Suppose you're building an app to keep track of your finances. Every day, you write down everything you spent money on. At the end of the month, you review your expenses and sum up how much you spent. So, you have lots of inserts and a few reads. Should you use an array or a list? 2 | 3 | > Because lists have a smaller run time for insertion operations than arrays do (`O(1)` vs. `O(n)`), you should use a list data structure for your finances. In addition, because each month you are reading every item in the list anyway, a list structure will also perform well in comparison to an array structure for read operations in this scenario as well. 4 | 5 | 2. Suppose you’re building an app for restaurants to take customer orders. Your app needs to store a list of orders. Servers keep adding orders to this list, and chefs take orders off the list and make them. It’s an order queue: servers add orders to the back of the queue, and the chef takes the first order off the queue and cooks it. 6 | 7 | Would you use an array or a linked list to implement this queue? (Hint: Linked lists are good for inserts/deletes, and arrays are good for random access. Which one are you going to be doing here?) 8 | 9 | > Because linked lists are better for insertions and deletions, a linked list would be the more appropriate data structure to use here. If you used an array, the entire array would have to be reallocated to a new slot in memory each time a new order was added to the beginning of the queue. Moreover, because the chef is always taking the first item off the queue, random access (which arrays are better suited for) isn't a requirement. 10 | 11 | 3. Let’s run a thought experiment. Suppose Facebook keeps a list of usernames. When someone tries to log in to Facebook, a search is done for their username. If their name is in the list of usernames, they can log in. People log in to Facebook pretty often, so there are a lot of searches through this list of usernames. Suppose Facebook uses binary search to search the list. Binary search needs random access—you need to be able to get to the middle of the list of usernames instantly. Knowing this, would you implement the list as an array or a linked list? 12 | 13 | > I would use a sorted array data structure to implement this binary search, because it is better suited to random access. A linked list would require iterating through a large portion of the list at each step of the binary search algorithm. 14 | 15 | 4. People sign up for Facebook pretty often, too. Suppose you decided to use an array to store the list of users. What are the downsides of an array for inserts? In particular, suppose you’re using binary search to search for logins. What happens when you add new users to an array? 16 | 17 | > In order to use a binary search to search for logins, Facebook would need to keep the array of usernames sorted. As a result, new usernames would need to be inserted in their sort order, and entire portions of the array would need to be reallocated to new indexes each time a new username was added. 18 | 19 | 5. In reality, Facebook uses neither an array nor a linked list to store user information. Let’s consider a hybrid data structure: an array of linked lists. You have an array with 26 slots. Each slot points to a linked list. For example, the first slot in the array points to a linked list containing all the usernames starting with a. The second slot points to a linked list containing all the usernames starting with b, and so on. 20 | 21 | Suppose Adit B signs up for Facebook, and you want to add them to the list. You go to slot 1 in the array, go to the linked list for slot 1, and add Adit B at the end. Now, suppose you want to search for Zakhir H. You go to slot 26, which points to a linked list of all the Z names. Then you search through that list to find Zakhir H. 22 | 23 | Compare this hybrid data structure to arrays and linked lists. Is it slower or faster than each for searching and inserting? You don’t have to give Big O run times, just whether the new data structure would be faster or slower. 24 | 25 | > The hybrid approach is significantly slower than the array approach for searching, because the hybrid approach potentially has to iterate over every username for a given letter, whereas in the array approach, the algorithm can use random access to immediately find the username it is looking for. However, the hybrid approach is significantly faster than the array approach at insertion, because new usernames can be added without reassigning the index of every other username for that letter. 26 | 27 | > Conversely, the hybrid approach is faster than the list approach for searching, since it eliminates several steps in the binary search by first using random access to narrow down possible results by the initial letter. However, the hybrid approach will take one additional step to insert usernames than the linked list approach, because the algorithm must first look up the correct list in the array. Their Big O run times, however, would be the same, because both are linear algorithms. 28 | -------------------------------------------------------------------------------- /02-selection-sort/selectionSort.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | no-param-reassign 3 | */ 4 | 5 | // NOTE: 6 | // - for loop is faster that forEach 7 | // - declaring swap ouside selectionSort is faster 8 | 9 | const swap = (array, firstIndex, secondIndex) => { 10 | const temp = array[firstIndex]; 11 | array[firstIndex] = array[secondIndex]; 12 | array[secondIndex] = temp; 13 | }; 14 | 15 | /** 16 | * Sorts an array in place based on a giving comparator function, using a selection sort algorithm (O(n^2)) 17 | * @param {Array} array The Array to sort 18 | * @param {Function} [compare=(a, b) => a -b] The comparator function 19 | * @return {Array} Returns the sorted Array 20 | */ 21 | const selectionSort = (array, compare = (a, b) => a - b) => { 22 | 23 | let minIndex = 0; 24 | 25 | for (let i = 0; i < array.length; i++) { 26 | 27 | minIndex = i; 28 | 29 | for (let j = i + 1; j < array.length; j++) { 30 | if (compare(array[minIndex], array[j]) > 0) minIndex = j; 31 | } 32 | 33 | if (i !== minIndex) swap(array, i, minIndex); 34 | 35 | } 36 | 37 | return array; 38 | 39 | }; 40 | 41 | module.exports = selectionSort; 42 | -------------------------------------------------------------------------------- /02-selection-sort/selectionSort.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | eslint-disable 3 | func-names, 4 | no-console, 5 | prefer-arrow-callback, 6 | */ 7 | 8 | const selectionSort = require('./selectionSort'); 9 | 10 | it(`selectionSort`, function() { 11 | 12 | const len = 1e4; 13 | const array = Array.from(Array(len), () => Math.random()); 14 | 15 | console.time(`\nselectionSort`); 16 | selectionSort(array); 17 | console.timeEnd(`\nselectionSort`); 18 | 19 | array.reduce((prev, current) => { 20 | expect(current > prev); 21 | return current; 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /03-recursion/exercises.md: -------------------------------------------------------------------------------- 1 | 1. Suppose I show you a call stack like this: 2 | 3 | ![picture of a call stack](stack.png) 4 | 5 | What information can you give me, just based on this call stack? 6 | 7 | > Based on this call stack, we can see that the first function invoked was called `greet`, and contained a variable named `name`, whose value was `maggie`. This `greet` function called another function, `greet2`, which retained the variable `name` and its value `maggie`. At the point in time at which this call stack was captured, the `greet2` function has not yet finished. 8 | 9 | 2. Suppose you accidentally write a recursive function that runs forever. As you saw, your computer allocates memory on the stack for each function call. What happens to the stack when your recursive function runs forever? 10 | 11 | > When a recursive function runs forever, the call stack continues to grow, eating up the system's memory. Eventually, the system will run out of available memory, causing an error to occur. 12 | -------------------------------------------------------------------------------- /03-recursion/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwhieb/grokking-algorithms/e23dda2e19501e56d82c20f9e501b24de367cd5b/03-recursion/stack.png -------------------------------------------------------------------------------- /04-quicksort/binarySearchRecursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Performs a recursive binary search for an item in an Array 3 | * @param {Array} array The Array to search 4 | * @param {Any} item The item to search for in the Array 5 | * @param {Integer} [start=0] The index to begin the search at 6 | * @param {Integer} [end=array.length-1] The index to end the search at 7 | * @param {Function} [compare=(a,b) => a-b] A comparator Function to compare the values of items in the Array 8 | * @return {Integer} Returns the index of the item in the Array, or `null` if not found. 9 | */ 10 | const binarySearchRecursive = ( 11 | array, 12 | item, 13 | start = 0, 14 | end = array.length - 1, 15 | compare = (a, b) => a - b 16 | ) => { 17 | 18 | if (start > end) return null; 19 | 20 | const mid = start + Math.floor((end - start) / 2); 21 | 22 | if (array[mid] === item) return mid; 23 | 24 | const comparison = Number(compare(array[mid], item)); 25 | 26 | if (comparison < 0) return binarySearchRecursive(array, item, mid + 1, end); 27 | if (comparison > 0) return binarySearchRecursive(array, item, start, mid - 1); 28 | 29 | }; 30 | 31 | module.exports = (array, item) => { 32 | return binarySearchRecursive(array, item); 33 | }; 34 | -------------------------------------------------------------------------------- /04-quicksort/binarySearchRecursive.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | prefer-arrow-callback, 4 | */ 5 | 6 | const binarySearchRecursive = require('./binarySearchRecursive'); 7 | 8 | it(`binarySearchRecursive`, function() { 9 | 10 | const len = 1e4; 11 | const array = Array.from(Array(len), (item, i) => i + 1); 12 | const num = Math.floor(Math.random() * len); 13 | 14 | console.time(`\nbinarySearchRecursive`); 15 | const result = binarySearchRecursive(array, num); 16 | console.timeEnd(`\nbinarySearchRecursive`); 17 | expect(result).toBe(num - 1); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /04-quicksort/countRecursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Recursively counts the values in an Array 3 | * @param {Array} array The Array to count 4 | * @return {Integer} Returns the count of the number of items in the Array 5 | */ 6 | const countRecursive = array => { 7 | if (!array.length) return 0; 8 | return 1 + countRecursive(array.slice(1)); 9 | }; 10 | 11 | module.exports = countRecursive; 12 | -------------------------------------------------------------------------------- /04-quicksort/countRecursive.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | prefer-arrow-callback, 4 | */ 5 | 6 | const countRecursive = require('./countRecursive'); 7 | 8 | it(`countRecursive`, function() { 9 | 10 | const len = 1e4; 11 | const array = Array.from(Array(len), () => Math.floor(Math.random() * 100)); 12 | 13 | console.time(`\ncountRecursive`); 14 | const count = countRecursive(array); 15 | console.timeEnd(`\ncountRecursive`); 16 | 17 | expect(count).toBe(array.length); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /04-quicksort/exercises.md: -------------------------------------------------------------------------------- 1 | 1. Write out the code for the recursive `sum` function. 2 | 3 | > See the code in `sumRecursive.js` and its test in `sumRecursive.test.js`. 4 | 5 | 2. Write a recursive function to count the number of items in a list. 6 | 7 | > See the code in `countRecursive.js` and its test in `countRecursive.test.js`. 8 | 9 | 3. Find the maximum number in a list. 10 | 11 | > See the code in `maxRecursive.js` and its test in `maxRecursive.test.js`. 12 | 13 | 4. Remember binary search from chapter 1? It’s a divide-and-conquer algorithm, too. Can you come up with the base case and recursive case for binary search? 14 | 15 | > See the code in `binarySearchRecursive.js` and its test in `binarySearchRecursive.test.js`. The base case is one in which the middle item in the array is equal to the target, in which case we simply return the index of that item. In the recursive case, we check whether the middle value is high or low, and then run the binary search on just the lower or upper half of the array accordingly. The search algorithm also has `start` and `end` parameters so as to maintain the indexes of the original array. 16 | 17 | How long would each of these operations take in Big O notation? 18 | 19 | 5. Printing the value of each element in an array. 20 | 21 | > This would take `O(n)` time, since the operation would have to be performed once for each item. 22 | 23 | 6. Doubling the value of each element in an array. 24 | 25 | > This would likewise take `O(n)` time, since the operation is performed once on each item. 26 | 27 | 7. Doubling the value of just the first element in an array. 28 | 29 | > This operation would take `O(1)` time, since the operation is only performed once, and not once for each item in the array. 30 | 31 | 8. Creating a multiplication table with all the elements in the array. So if you array is [2, 3, 7, 8, 10], you first multiply every element by 2, then multiply every element by 3, then by 7, and so on. 32 | 33 | > This operation would take `O(n^2)` time, because every element must be multiplied against every other element, and thus the operation is exponential. 34 | -------------------------------------------------------------------------------- /04-quicksort/maxRecursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Recursively finds the maximum value in an Array 3 | * @param {[type]} array [description] 4 | * @return {[type]} [description] 5 | */ 6 | const maxRecursive = array => { 7 | 8 | const firstItem = array[0]; 9 | 10 | if (!array.length) return null; 11 | else if (array.length === 1) return firstItem; 12 | else if (array.length === 2) return firstItem > array[1] ? firstItem : array[1]; 13 | 14 | const subMax = maxRecursive(array.slice(1)); 15 | return firstItem > subMax ? firstItem : subMax; 16 | 17 | }; 18 | 19 | module.exports = maxRecursive; 20 | -------------------------------------------------------------------------------- /04-quicksort/maxRecursive.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | prefer-arrow-callback, 4 | */ 5 | 6 | const maxRecursive = require('./maxRecursive'); 7 | 8 | it(`maxRecursive`, function() { 9 | 10 | const len = 1e4; 11 | const array = Array.from(Array(len), () => Math.random()); 12 | 13 | console.time(`\nmaxRecursive`); 14 | const max = maxRecursive(array); 15 | console.timeEnd(`\nmaxRecursive`); 16 | 17 | expect(max).toBe(Math.max(...array)); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /04-quicksort/quicksort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A quicksort algorithm 3 | * @param {Array} array The Array to sort 4 | * @param {Function} [compare=(a,b) => a-b] A comparator to use for sorting 5 | * @return {Array} Returns the original Array, sorted 6 | */ 7 | const quicksort = (array, compare = (a, b) => a - b) => { 8 | 9 | if (array.length < 2) return array; 10 | 11 | const pivot = array[0]; 12 | const arr = array.slice(1); 13 | const less = arr.filter(item => compare(item, pivot) <= 0); 14 | const greater = arr.filter(item => compare(item, pivot) > 0); 15 | 16 | array.length = 0; 17 | // return a single function rather than use the spread operator to optimize tail call 18 | return array.concat(quicksort(less), [pivot], quicksort(greater)); 19 | 20 | }; 21 | 22 | module.exports = quicksort; 23 | -------------------------------------------------------------------------------- /04-quicksort/quicksort.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | prefer-arrow-callback, 4 | */ 5 | 6 | const quicksort = require('./quicksort'); 7 | 8 | it(`quicksort`, function() { 9 | 10 | const len = 1e4; 11 | const array = Array.from(Array(len), () => Math.random()); 12 | 13 | console.time(`\nquicksort`); 14 | const sortedArray = quicksort(array); 15 | console.timeEnd(`\nquicksort`); 16 | 17 | sortedArray.reduce((prev, current) => { 18 | expect(current > prev); 19 | return current; 20 | }); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /04-quicksort/sumRecursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Recursively sums the values of an Array. NOTE: The result will be different with each invocation if floating point numbers are used. 3 | * @param {Array} array The Array of values to add 4 | * @return {Number} Returns the sum of the values in the Array 5 | */ 6 | const sumRecursive = array => { 7 | if (!array.length) return 0; 8 | return Number(array[0] || 0) + sumRecursive(array.slice(1)); 9 | }; 10 | 11 | module.exports = sumRecursive; 12 | -------------------------------------------------------------------------------- /04-quicksort/sumRecursive.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | no-console, 4 | prefer-arrow-callback, 5 | */ 6 | 7 | const sumRecursive = require('./sumRecursive'); 8 | 9 | it(`sumRecursive`, function() { 10 | 11 | const len = 1e4; 12 | const array = Array.from(Array(len), () => Math.floor(Math.random() * 100)); 13 | 14 | console.time(`\nsumRecursive`); 15 | const sum = sumRecursive(array); 16 | console.timeEnd(`\nsumRecursive`); 17 | 18 | expect(sum).toBe(array.reduce((a, b) => a + b)); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /05-hash-tables/exercises.md: -------------------------------------------------------------------------------- 1 | Which of these functions are consistent? 2 | 3 | 1. `f(x) = 1 // returns "1" for all input` 4 | 5 | > This function is consistent because it always returns the same value. 6 | 7 | 2. `f(x) = rand() // returns a random number every time` 8 | 9 | > This function is inconsistent. The value returned is different every time it is evaluated. 10 | 11 | 3. `f(x) = next_empty_slot() // returns the index of the next empty slot in the hash table` 12 | 13 | > This function is not consistent. If new items are added to the hash table, the index of the next empty slot will change. 14 | 15 | 4. `f(x) = len(x) // uses the length of the string as the index` 16 | 17 | > This function is consistent. A string of a given length will always return the same value. 18 | 19 | Suppose you have these four hash functions that work with strings: 20 | 21 | * a. Return “1” for all input. 22 | 23 | * b. Use the length of the string as the index. 24 | 25 | * c. Use the first character of the string as the index. So, all strings starting with a are hashed together, and so on. 26 | 27 | * d. Map every letter to a prime number: a = 2, b = 3, c = 5, d = 7, e = 11, and so on. For a string, the hash function is the sum of all the characters modulo the size of the hash. For example, if your hash size is 10, and the string is “bag”, the index is 3 + 2 + 17 % 10 = 22 % 10 = 2. 28 | 29 | For each of these examples, which hash functions would provide a good distribution? Assume a hash table size of 10 slots. 30 | 31 | > Function (a) would not work well for any of these data sets. Every insert operation would cause a collision, and all the items would therefore be stored in an linked list, making retrieval significantly slower. 32 | 33 | 5. A phonebook where the keys are names and values are phone numbers. The names are as follows: Esther, Ben, Bob, and Dan. 34 | 35 | > Function (b) would not work well for this data, because three of the names have the same length. The data in the hash table would therefore be unevenly distributed between just two indexes (`3` and `6`). 36 | 37 | > Function (c) would work better than (b), because it only causes 1 collision for this set of data. However, function (d) seems the best choice for this data set. The calculations below show that this hash function distributes all four names into a different slot, and generally minimizes the risk of collisions for additional names that might be added. 38 | 39 | ``` 40 | Esther: (11 + 67 + 71 + 19 + 11 + 61) % 10 = 0 41 | Ben: (3 + 11 + 43) % 10 = 7 42 | Bob: (3 + 47 + 3) % 10 = 3 43 | Dan: (7 + 2 + 43) % 10 = 2 44 | ``` 45 | 46 | 6. A mapping from battery size to power. The sizes are A, AA, AAA, and AAAA. 47 | 48 | > Hash functions (a) and (c) would produce the same result for this data set - namely, that all four items would be stored in the same slot, causing a collision on every insert. Function (b), however, is perfectly suited to this data set, because it would result in each item being stored in its own slot in the hash table, avoiding any collisions. 49 | 50 | 7. A mapping from book titles to authors. The titles are _Maus_, _Fun Home_, and _Watchmen_. 51 | 52 | > Function (c) is probably the best hashing algorithm to use in this case, because for this particular data set it avoids collisions. However, if data continues to be added to the hash table, function (c) has the drawback that some names are more common than others, and some letters as well. The names in the hash table would not likely be evenly distributed if more data were continually added. Function (d) would also presumably work quite well, as it did for the names in Question 5. 53 | 54 | > In general, hash function (d) seems to be a good approach, because it adjusts the index depending on both the length and the characters of the key. 55 | -------------------------------------------------------------------------------- /06-breadth-first-search/breadthFirstSearch.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | no-continue, 3 | */ 4 | 5 | /** 6 | * A breadth-first graph searching algorithm. The graph must be an Object/Array whose values are Arrays. (A nested array graph is most typical.) 7 | * @param {Object|Array} graph The graph to search. Each value in the graph must be an Array. 8 | * @param {Function} match A matching function that accepts a node as its argument and returns true when the correct node is found 9 | * @param {String} [start] The node to begin searching from 10 | * @return {Any} Returns the matched node, or null if no match was found 11 | */ 12 | const breadthFirstSearch = (graph, match, start = Object.keys(graph)[0]) => { 13 | 14 | const parents = new Map; 15 | const visited = new Set; 16 | const network = new Map(Object.entries(graph)); 17 | const begin = start ? [start] : Array.from(network.keys())[0]; 18 | const queue = [...begin]; 19 | 20 | const getPathAndDistance = end => { 21 | let current = end; 22 | let distance = 0; 23 | const path = []; 24 | while (parents.has(current)) { 25 | distance++; 26 | current = parents.get(current); 27 | path.push(current); 28 | } 29 | return { 30 | distance, 31 | path, 32 | }; 33 | }; 34 | 35 | while (queue.length) { 36 | 37 | const current = queue.shift(); 38 | 39 | if (visited.has(current)) continue; 40 | 41 | if (match(current)) { 42 | 43 | const { path, distance } = getPathAndDistance(current); 44 | 45 | return { 46 | distance, 47 | match: current, 48 | path, 49 | }; 50 | } 51 | 52 | visited.add(current); 53 | 54 | graph[current].forEach(neighbor => { 55 | if (!visited.has(neighbor)) queue.push(neighbor); 56 | if (!parents.has(neighbor)) parents.set(neighbor, current); 57 | }); 58 | 59 | } 60 | 61 | return { match: null }; 62 | 63 | }; 64 | 65 | module.exports = breadthFirstSearch; 66 | -------------------------------------------------------------------------------- /06-breadth-first-search/breadthFirstSearch.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | prefer-arrow-callback, 4 | */ 5 | 6 | const names = require('./names'); 7 | const race = require('./race'); 8 | const words = require('./words'); 9 | 10 | const breadthFirstSearch = require('./breadthFirstSearch'); 11 | 12 | it(`breadthFirstSearch: names`, function() { 13 | 14 | console.time(`\nbreadthFirstSearch (names)`); 15 | const result = breadthFirstSearch(names, item => item.endsWith(`m`), `you`); 16 | console.timeEnd(`\nbreadthFirstSearch (names)`); 17 | 18 | expect(result.match).toBe(`thom`); 19 | expect(result.distance).toBe(2); 20 | 21 | }); 22 | 23 | it(`breadthFirstSearch: race`, function() { 24 | 25 | const finish = 5; 26 | 27 | console.time(`\nbreadthFirstSearch (race)`); 28 | const result = breadthFirstSearch(race, item => item === finish, 0); 29 | console.timeEnd(`\nbreadthFirstSearch (race)`); 30 | 31 | expect(result.match).toBe(finish); 32 | expect(result.distance).toBe(2); 33 | 34 | }); 35 | 36 | it(`breadthFirstSearch: words`, function() { 37 | 38 | console.time(`\nbreadthFirstSearch (words)`); 39 | const result = breadthFirstSearch(words, item => item === `bat`, `cab`); 40 | console.timeEnd(`\nbreadthFirstSearch (words)`); 41 | 42 | expect(result.match).toBe(`bat`); 43 | expect(result.distance).toBe(2); 44 | 45 | }); 46 | 47 | it(`breadthFirstSearch: no match`, function() { 48 | 49 | console.time(`\nbreadthFirstSearch (no match)`); 50 | const result = breadthFirstSearch(words, item => item === `Zelda`); 51 | console.timeEnd(`\nbreadthFirstSearch (no match)`); 52 | 53 | expect(result.match).toBe(null); 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /06-breadth-first-search/exercise-6.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwhieb/grokking-algorithms/e23dda2e19501e56d82c20f9e501b24de367cd5b/06-breadth-first-search/exercise-6.5.png -------------------------------------------------------------------------------- /06-breadth-first-search/exercises.md: -------------------------------------------------------------------------------- 1 | Run the breadth-first search algorithm on each of these graphs to find the solution. 2 | 3 | > See the code in `breadthFirstSearch.js` and `breadthFirstSearch.test.js`, along with the accompanying data that form the graphs in `names.json`, `race.json` and `words.json`. The algorithm accepts either an Object or Array as a graph, a matching function, and an optional starting point. 4 | 5 | 1. Find the length of the shortest path from start to finish. 6 | 7 | ![graph for exercise 6.1](graph-6.1.png) 8 | 9 | 2. Find the length of the shortest path from "cab" to "bat". 10 | 11 | ![graph for exercise 6.2](graph-6.2.png) 12 | 13 | Here’s a small graph of my morning routine: 14 | 15 | ![graph for exercise 6.3](graph-6.3.png) 16 | 17 | 3. For these three lists, mark whether each one is valid or invalid: 18 | 19 | * **List A** 20 | 1. wake up 21 | 2. shower 22 | 3. eat breakfast 23 | 4. brush teeth 24 | 25 | * **List B** 26 | 1. wake up 27 | 2. brush teeth 28 | 3. eat breakfast 29 | 4. shower 30 | 31 | * **List C** 32 | 1. shower 33 | 2. wake up 34 | 3. brush teeth 35 | 4. eat breakfast 36 | 37 | > List A is invalid because `eat breakfast` cannot be ordered before `brush teeth`. 38 | 39 | > List B is valid. No items violate the ordering of the graph. 40 | 41 | > List C is invalid. `wake up` must be the first item in the list. 42 | 43 | 4. Here's a larger graph. Make a valid list for this graph. 44 | 45 | ![graph for exercise 6.4](graph-6.4.png) 46 | 47 | > 1. wake up 48 | > 2. exercise 49 | > 3. shower 50 | > 4. brush teeth 51 | > 5. get dressed 52 | > 6. eat breakfast 53 | > 7. pack lunch 54 | 55 | 5. Which of the following graphs are also trees? 56 | 57 | ![choices of graphs for exercise 6.5](exercise-6.5.png) 58 | 59 | > Graphs A and C are trees, but graph B is not, because graph B has edges that point back to earlier nodes. 60 | -------------------------------------------------------------------------------- /06-breadth-first-search/graph-6.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwhieb/grokking-algorithms/e23dda2e19501e56d82c20f9e501b24de367cd5b/06-breadth-first-search/graph-6.1.png -------------------------------------------------------------------------------- /06-breadth-first-search/graph-6.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwhieb/grokking-algorithms/e23dda2e19501e56d82c20f9e501b24de367cd5b/06-breadth-first-search/graph-6.2.png -------------------------------------------------------------------------------- /06-breadth-first-search/graph-6.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwhieb/grokking-algorithms/e23dda2e19501e56d82c20f9e501b24de367cd5b/06-breadth-first-search/graph-6.3.png -------------------------------------------------------------------------------- /06-breadth-first-search/graph-6.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwhieb/grokking-algorithms/e23dda2e19501e56d82c20f9e501b24de367cd5b/06-breadth-first-search/graph-6.4.png -------------------------------------------------------------------------------- /06-breadth-first-search/names.json: -------------------------------------------------------------------------------- 1 | { 2 | "you": ["alice", "bob", "claire"], 3 | "bob": ["anuj", "peggy"], 4 | "alice": ["peggy"], 5 | "claire": ["thom", "jonny"], 6 | "anuj": [], 7 | "peggy": [], 8 | "thom": [], 9 | "jonny": [] 10 | } 11 | -------------------------------------------------------------------------------- /06-breadth-first-search/race.json: -------------------------------------------------------------------------------- 1 | [ 2 | [1,2], 3 | [3,5], 4 | [3,4], 5 | [], 6 | [5], 7 | [] 8 | ] 9 | -------------------------------------------------------------------------------- /06-breadth-first-search/words.json: -------------------------------------------------------------------------------- 1 | { 2 | "cab": ["cat", "car"], 3 | "mat": ["bat"], 4 | "cat": ["mat", "bat"], 5 | "car": ["cat", "bar"], 6 | "bat": [], 7 | "bar": ["bat"] 8 | } 9 | -------------------------------------------------------------------------------- /07-dijkstras-algorithm/dijkstrasAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A JavaScript implementation of Dijkstra's Algorithm that returns a Map of distances from the start node 3 | * @param {Object} graph A hash table of nodes which maps to the neighbors and distance to those neighbors 4 | * @param {String} [start=Object.keys(graph)[0]] The node to calculate distances from 5 | * @return {Map} Returns a Map listing the distance to each node from the start node 6 | * @example 7 | *const graph = { 8 | start: { 9 | A: 6, 10 | B: 2, 11 | }, 12 | A: { 13 | finish: 1, 14 | }, 15 | B: { 16 | A: 3, 17 | finish: 5, 18 | }, 19 | finish: {}, 20 | }; 21 | 22 | const distances = dijkstrasAlgorithm(graph, `start`); 23 | 24 | distances.get(`finish`) // => 6 25 | */ 26 | const dijkstrasAlgorithm = (graph, start = Object.keys(graph)[0]) => { 27 | 28 | const costs = new Map; 29 | const parents = new Map; 30 | const visited = new Set; 31 | 32 | const getCheapestNode = () => { 33 | 34 | let lowestCost = Infinity; 35 | let cheapestNode = null; 36 | 37 | for (const [node, cost] of costs) { 38 | if (cost < lowestCost && !visited.has(node)) { 39 | lowestCost = cost; 40 | cheapestNode = node; 41 | } 42 | } 43 | 44 | return cheapestNode; 45 | 46 | }; 47 | 48 | Object.keys(graph).forEach(node => costs.set(node, Infinity)); 49 | Object.entries(graph[start]).forEach(([node, cost]) => costs.set(node, cost)); 50 | costs.set(start, 0); 51 | 52 | let node = getCheapestNode(); 53 | 54 | while (node) { 55 | 56 | const cost = costs.get(node); 57 | const neighbors = graph[node]; 58 | 59 | for (const [neighbor, neighborCost] of Object.entries(neighbors)) { 60 | 61 | const newCost = cost + neighborCost; 62 | 63 | if (newCost < costs.get(neighbor)) { 64 | costs.set(neighbor, newCost); 65 | parents.set(neighbor, node); 66 | } 67 | 68 | } 69 | 70 | visited.add(node); 71 | node = getCheapestNode(); 72 | 73 | } 74 | 75 | return costs; 76 | 77 | }; 78 | 79 | module.exports = dijkstrasAlgorithm; 80 | -------------------------------------------------------------------------------- /07-dijkstras-algorithm/dijkstrasAlgorithm.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | no-magic-numbers, 4 | prefer-arrow-callback, 5 | */ 6 | 7 | const dijkstrasAlgorithm = require('./dijkstrasAlgorithm'); 8 | const graph = require('./graph'); 9 | const A = require('./graph-a'); 10 | const B = require('./graph-b'); 11 | 12 | it(`Dijkstra's Algorithm`, function() { 13 | 14 | console.time(`\nDijkstra's Algorithm`); 15 | const costs = dijkstrasAlgorithm(graph, `start`); 16 | console.timeEnd(`\nDijkstra's Algorithm`); 17 | 18 | expect(costs.get(`finish`)).toBe(6); 19 | 20 | }); 21 | 22 | it(`Dijkstra's Algorithm: Graph A`, function() { 23 | 24 | console.time(`\nDijkstra's Algorithm`); 25 | const costs = dijkstrasAlgorithm(A, `A`); 26 | console.timeEnd(`\nDijkstra's Algorithm`); 27 | 28 | expect(costs.get(`F`)).toBe(8); 29 | 30 | }); 31 | 32 | it(`Dijkstra's Algorithm: Graph B`, function() { 33 | 34 | console.time(`\nDijkstra's Algorithm`); 35 | const costs = dijkstrasAlgorithm(B, `A`); 36 | console.timeEnd(`\nDijkstra's Algorithm`); 37 | 38 | expect(costs.get(`E`)).toBe(60); 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /07-dijkstras-algorithm/exercise-7.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwhieb/grokking-algorithms/e23dda2e19501e56d82c20f9e501b24de367cd5b/07-dijkstras-algorithm/exercise-7.1.png -------------------------------------------------------------------------------- /07-dijkstras-algorithm/exercises.md: -------------------------------------------------------------------------------- 1 | 1. In each of these graphs, what is the weight of the shortest path from start to finish? 2 | 3 | ![graphs for exercise 7.1](exercise-7.1.png) 4 | 5 | > The graphs for these problems are stored in `graph-a.json` and `graph-b.json` respectively. Dijkstra's Algorithm is run on these graphs, and the output checked, in `dijkstrasAlgorithm.test.js`. 6 | 7 | > In graph C there is no shortest path, because one of the edges has a negative weight (`-1`). 8 | 9 | > In graph A, the shortest path from the start node to the finish node has a weight of `8`. 10 | 11 | > In graph B, the shortest path from the start node to the finish node has a weight of `60`. 12 | -------------------------------------------------------------------------------- /07-dijkstras-algorithm/graph-a.json: -------------------------------------------------------------------------------- 1 | { 2 | "A": { 3 | "B": 5, 4 | "C": 2 5 | }, 6 | "B": { 7 | "D": 4, 8 | "E": 2 9 | }, 10 | "C": { 11 | "B": 8, 12 | "E": 7 13 | }, 14 | "D": { 15 | "E": 6, 16 | "F": 3 17 | }, 18 | "E": { 19 | "F": 1 20 | }, 21 | "F": {} 22 | } 23 | -------------------------------------------------------------------------------- /07-dijkstras-algorithm/graph-b.json: -------------------------------------------------------------------------------- 1 | { 2 | "A": { 3 | "B": 10 4 | }, 5 | "B": { 6 | "D": 20 7 | }, 8 | "C": { 9 | "B": 1 10 | }, 11 | "D": { 12 | "C": 1, 13 | "E": 30 14 | }, 15 | "E": {} 16 | } 17 | -------------------------------------------------------------------------------- /07-dijkstras-algorithm/graph.json: -------------------------------------------------------------------------------- 1 | { 2 | "start": { 3 | "A": 6, 4 | "B": 2 5 | }, 6 | "A": { 7 | "finish": 1 8 | }, 9 | "B": { 10 | "A": 3, 11 | "finish": 5 12 | }, 13 | "finish": {} 14 | } 15 | -------------------------------------------------------------------------------- /08-greedy-algorithms/exercises.md: -------------------------------------------------------------------------------- 1 | 1. You work for a furniture company, and you have to ship furniture all over the country. You need to pack your truck with boxes. All the boxes are of different sizes, and you’re trying to maximize the space you use in each truck. How would you pick boxes to maximize space? Come up with a greedy strategy. Will that give you the optimal solution? 2 | 3 | > A greedy algorithm for this problem could always select the largest box first, and repeat until the space is full. This will not give you the optimal solution. 4 | 5 | 2. You’re going to Europe, and you have seven days to see everything you can. You assign a point value to each item (how much you want to see it) and estimate how long it takes. How can you maximize the point total (seeing all the things you really want to see) during your stay? Come up with a greedy strategy. Will that give you the optimal solution? 6 | 7 | > A greedy solution to this problem would entail seeing the thing with the highest point value first, and repeating this procedure until you are out of time. This would not necessarily yield the optimal solution. 8 | 9 | For each of these algorithms, say whether it’s a greedy algorithm or not. 10 | 11 | 3. Quicksort 12 | 13 | > Quicksort is not a greedy algorithm, because the chosen pivot may not be optimal for that particular iteration, 14 | 15 | 4. Breadth-first search 16 | 17 | > Breadth-first search is a greedy algorithm, because it checks the closest neighbors first. 18 | 19 | 5. Dijkstra's Algorithm 20 | 21 | > Dijkstra's Algorithm is a greedy algorithm, because at each step it checks the shortest/cheapest node first. 22 | 23 | 6. A postman needs to deliver to 20 homes. He needs to find the shortest route that goes to all 20 homes. Is this an NP-complete problem? 24 | 25 | > Yes, this is an NP-complete problem because it involves finding the shortest path between several points, rather than just two. 26 | 27 | 7. Finding the largest clique in a set of people (a clique is a set of people who all know each other). Is this an NP-complete problem? 28 | 29 | > Yes, this is an NP-complete problem because it is a type of set-covering problem. 30 | 31 | 8. You’re making a map of the USA, and you need to color adjacent states with different colors. You have to find the minimum number of colors you need so that no two adjacent states are the same color. Is this an NP-complete problem? 32 | 33 | > Yes, this is an NP-complete problem, because it requires calculating every possible combination of states and colors. 34 | -------------------------------------------------------------------------------- /08-greedy-algorithms/setCoverage.js: -------------------------------------------------------------------------------- 1 | const stateData = require('./states'); 2 | const stationData = require('./stations'); 3 | 4 | let states = new Set(stateData); 5 | const stations = new Map(Object.entries(stationData)); 6 | 7 | stations.forEach((val, key) => stations.set(key, new Set(val))); 8 | 9 | module.exports = () => { 10 | 11 | const finalSet = new Set; 12 | 13 | while (states.size) { 14 | 15 | let bestStation = null; 16 | let statesCovered = new Set; 17 | 18 | for (const [station, stationStates] of stations) { 19 | 20 | const covered = new Set([...states].filter(state => stationStates.has(state))); 21 | 22 | if (covered.size > statesCovered.size) { 23 | bestStation = station; 24 | statesCovered = covered; 25 | } 26 | 27 | } 28 | 29 | states = new Set([...states].filter(state => !statesCovered.has(state))); 30 | finalSet.add(bestStation); 31 | 32 | } 33 | 34 | return finalSet; 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /08-greedy-algorithms/setCoverage.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable 2 | func-names, 3 | prefer-arrow-callback, 4 | */ 5 | 6 | const getSetCoverage = require('./setCoverage'); 7 | 8 | it(`setCoverage`, function() { 9 | 10 | console.time(`\nsetCoverage`); 11 | const result = getSetCoverage(); 12 | console.timeEnd(`\nsetCoverage`); 13 | 14 | expect(result.size).toBe(4); 15 | expect(result.has(`kone`)).toBe(true); 16 | expect(result.has(`ktwo`)).toBe(true); 17 | expect(result.has(`kthree`)).toBe(true); 18 | expect(result.has(`kfive`)).toBe(true); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /08-greedy-algorithms/states.json: -------------------------------------------------------------------------------- 1 | [ 2 | "mt", 3 | "wa", 4 | "or", 5 | "id", 6 | "nv", 7 | "ut", 8 | "ca", 9 | "az" 10 | ] 11 | -------------------------------------------------------------------------------- /08-greedy-algorithms/stations.json: -------------------------------------------------------------------------------- 1 | { 2 | "kone": ["id", "nv", "ut"], 3 | "ktwo": ["wa", "id", "mt"], 4 | "kthree": ["or", "nv", "ca"], 5 | "kfour": ["nv", "ut"], 6 | "kfive": ["ca", "az"] 7 | } 8 | -------------------------------------------------------------------------------- /09-dynamic-programming/exercises.md: -------------------------------------------------------------------------------- 1 | 1. Suppose you can steal another item: an MP3 player. It weighs 1 lb and is worth $1,000. Should you steal it? 2 | 3 | > Yes. Stealing the MP3 player would increase the maximum value of the stolen goods from $4,000 to $4,500, as shown in the table below. 4 | 5 | Item | 1 | 2 | 3 | 4 6 | :----: | :--------: | :---------: | :----------: | :---------: 7 | guitar | $1,500 (G) | $1,500 (G) | $1,500 (G) | $1,500 (G) 8 | stereo | $1,500 (G) | $1,500 (G) | $1,500 (G) | $3,000 (S) 9 | laptop | $1,500 (G) | $1,500 (G) | $2,000 (L) | $3,500 (LG) 10 | iPhone | $2,000 (I) | $3,500 (IG) | $3,500 (IG) | $4,000 (IL) 11 | MP3 | $2,000 (I) | $3,500 (IG) | $4,500 (MIG) | $4,500 (MIG) 12 | 13 | 2. Suppose you’re going camping. You have a knapsack that will hold 6 lb, and you can take the following items. Each has a value, and the higher the value, the more important the item is. 14 | 15 | Item | Weight | Value 16 | :----: | :----: | :---: 17 | water | 3 | 10 18 | book | 1 | 3 19 | food | 2 | 9 20 | jacket | 2 | 5 21 | camera | 1 | 6 22 | 23 | What’s the optimal set of items to take on your camping trip? 24 | 25 | > The optimal set of items to take on the camping trip are the `camera`, `food`, and `water`, for a total value of `25`. This is shown in the dynamic programming table below. 26 | 27 | Item | 1 lb. | 2 lb. | 3 lb. | 4 lb. | 5 lb. | 6 lb. 28 | :----: | :---: | :----: | :-----: | :------: | :------: | :-------: 29 | book | 3 (b) | 3 (b) | 3 (b) | 3 (b) | 3 (b) | 3 (b) 30 | camera | 6 (c) | 9 (bc) | 9 (bc) | 9 (bc) | 9 (bc) | 9 (bc) 31 | jacket | 6 (c) | 9 (bc) | 11 (cj) | 14 (bcj) | 14 (bcj) | 14 (bcj) 32 | food | 6 (c) | 9 (bc) | 15 (cf) | 18 (bcf) | 20 (cjf) | 21 (bcjf) 33 | water | 6 (c) | 9 (bc) | 15 (cf) | 18 (bcf) | 20 (cjf) | 25 (cfw) 34 | 35 | 3. Draw and fill in the grid to calculate the longest common substring between blue and clues. 36 | 37 |   | C | L | U | E | S 38 | ------ | - | - | - | - | - 39 | **B** | 0 | 0 | 0 | 0 | 0 40 | **L** | 0 | 1 | 0 | 0 | 0 41 | **U** | 0 | 0 | 2 | 0 | 0 42 | **E** | 0 | 0 | 0 | 3 | 0 43 | 44 | > The longest common substring is 3 characters long. 45 | -------------------------------------------------------------------------------- /10-k-nearest-neighbors/exercises.md: -------------------------------------------------------------------------------- 1 | 1. In the Netflix example, you calculated the distance between two different users using the distance formula. But not all users rate movies the same way. Suppose you have two users, Yogi and Pinky, who have the same taste in movies. But Yogi rates any movie he likes as a 5, whereas Pinky is choosier and reserves the 5s for only the best. They’re well matched, but according to the distance algorithm, they aren’t neighbors. How would you take their different rating strategies into account? 2 | 3 | > To take the different rating strategies of users into account, you could normalize their ratings. 4 | 5 | 2. Suppose Netflix nominates a group of “influencers.” For example, Quentin Tarantino and Wes Anderson are influencers on Netflix, so their ratings count for more than a normal user’s. How would you change the recommendations system so it’s biased toward the ratings of influencers? 6 | 7 | > You could use a weighted average when calculating the average rating of a movie, so that the influencers are given a greater weighting than non-influencers. 8 | 9 | 3. Netflix has millions of users. The earlier example looked at the five closest neighbors for building the recommendations system. Is this too low? Too high? 10 | 11 | > Using only the five closest neighbors is too low a number for a k-nearest neighbors algorithm. A good rule of thumb for determining `k` is `k = sqrt(N)`, where `N` is the total number of data points. So if Netflix had 10,000,000 users, it should look at approximately 3,000 nearest neighbors. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Daniel W. Hieber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grokking Algorithms 2 | My JavaScript implementation of the exercises in the book [*Grokking algorithms: An illustrated guide for programmers and curious people*](https://www.amazon.com/Grokking-Algorithms-illustrated-programmers-curious/dp/1617292230/ref=sr_1_1?ie=UTF8&qid=1498429126&sr=8-1&keywords=grokking+algorithms), by Aditya Y. Bhargava. 3 | -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_files": [ 3 | "**/*.test.js" 4 | ], 5 | "stopSpecOnExpectationFailure": false, 6 | "random": true 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "jasmine JASMINE_CONFIG_PATH=jasmine.json" 4 | } 5 | } 6 | --------------------------------------------------------------------------------