├── .gitignore ├── README.md ├── data-structures ├── binarySearchTree.js ├── graph.js ├── hashTable.js ├── heap.js ├── linkedList.js ├── queue.js ├── set.js ├── stack.js └── tree.js ├── recursion ├── README.md ├── factorial.js ├── fibonacci.js ├── flatten.js ├── greatestCommonDivisor.js ├── paintFill.js ├── pathCount.js ├── recursionIntro.js ├── reverse.js └── stringPermutation.js ├── searching-algorithms └── binarySearchArray.js └── sorting-algorithms ├── bubble.js ├── heap.js ├── insertion.js ├── merge.js ├── quick.js └── selection.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | test* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exercises for Intro to Algorithms and Data Structures in JavaScript 2 | 3 | Welcome to the exercises. This is where the magic happens! 4 | 5 | ### Monday - June 13th 6 | 1. Implement a stack data structure: 7 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/stack.js 8 | - Note: only do the first exercise after you implement the stack 9 | 2. Create a queue data structure: 10 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/queue.js 11 | - Note: no need to attempt the last exercise. Come back to it after we cover breadth-first search :) 12 | 3. Start off with some intro to recursion problems 13 | - https://github.com/kuychaco/algoClass/blob/master/recursion/recursionIntro.js 14 | 4. If you want a challenge, attempt some popular recursion interview questions: 15 | - https://github.com/kuychaco/algoClass/tree/master/recursion 16 | - Note: some of these will only click after we go through sorting, graphs & trees later this week. 17 | 18 | ### Tuesday - June 14th 19 | 1. Implement the elementary sorting algorithms (bubble, insertion, selection): 20 | - https://github.com/kuychaco/algoClass/tree/master/sorting-algorithms 21 | 2. Implement Mergesort 22 | - https://github.com/kuychaco/algoClass/blob/master/sorting-algorithms/merge.js 23 | 3. Implement Quicksort 24 | - https://github.com/kuychaco/algoClass/blob/master/sorting-algorithms/quick.js 25 | 4. [Bonus] Try out Heapsort 26 | - https://github.com/kuychaco/algoClass/blob/master/sorting-algorithms/heap.js 27 | 28 | ###Wednesday - June 15th 29 | 1. Implement a Linked List: 30 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/linkedList.js 31 | 2. Create a tree data structure: 32 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/tree.js 33 | - Note: Hold off on Depth First Search and Breadth First Search because we haven't covered that yet. 34 | 3. This is a catch up day so use the rest of the afternoon to finish up exercises from previous days and review anything that was confusing. 35 | 36 | ### Thursday - June 16th 37 | 1. Binary Search Tree 38 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/binarySearchTree.js 39 | 2. Binary Search Array 40 | - https://github.com/kuychaco/algoClass/blob/master/searching-algorithms/binarySearchArray.js 41 | 42 | ### Friday - June 17th 43 | 1. Binary Search Tree (delete nodes) 44 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/binarySearchTree.js 45 | 2. Implement a graph 46 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/graph.js 47 | 3. DFS for graph 48 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/graph.js 49 | 4. BFS for graph 50 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/graph.js 51 | 5. [Bonus] BFS/DFS for tree 52 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/tree.js 53 | 54 | ### Saturday - June 18th 55 | 1. Create your own Hash Tables! Watch out for those collisions! 56 | - https://github.com/kuychaco/algoClass/blob/master/data-structures/hashTable.js 57 | -------------------------------------------------------------------------------- /data-structures/binarySearchTree.js: -------------------------------------------------------------------------------- 1 | /* 2 | BINARY SEARCH TREES 3 | 4 | Abstract data type 5 | A binary search tree is a tree with the additional constraints: 6 | - each node has only two child nodes (node.left and node.right) 7 | - all the values in the left subtree of a node are less than or equal to the value of the node 8 | - all the values in the right subtree of a node are greater than the value of the node 9 | 10 | *** Operations: 11 | 12 | bsTree.insert(value) 13 | => bsTree (return for chaining purposes) 14 | Insert value into correct position within tree 15 | 16 | bsTree.contains(value) 17 | => true/false 18 | Return true if value is in tree, false if not 19 | 20 | bsTree.traverseDepthFirst_inOrder(callback) 21 | => undefined 22 | Invoke the callback for every node in a depth-first in-order (visit left branch, then current node, than right branch) 23 | Note: In-Order traversal is most common type for binary trees. For binary search tree, this visits the nodes in ascending order (hence the name). 24 | 25 | bsTree.traverseDepthFirst_preOrder(callback) 26 | => undefined 27 | Invoke the callback for every node in a depth-first pre-order (visits current node before its child nodes) 28 | 29 | bsTree.traverseDepthFirst_postOrder(callback) 30 | => undefined 31 | Invoke the callback for every node in a depth-first post-order (visit the current node after its child nodes) 32 | 33 | bsTree.isValid() 34 | => returns true if BST is a valid BST otherwise returns false. This method is useful for checking your other methods. 35 | 36 | bsTree.removeNode(value) 37 | => node 38 | Remove node from tree. 39 | 40 | bsTree.checkIfFull() 41 | => true/false 42 | A binary tree is full if every node has either zero or two children (no nodes have only one child) 43 | 44 | bsTree.checkIfBalanced() 45 | => true/false 46 | For this exercise, let's say that a tree is balanced if the minimum height and the maximum height differ by no more than 1. The height for a branch is the number of levels below the root. 47 | 48 | 49 | *** Additional Exercises: 50 | A binary search tree was created by iterating over an array and inserting each element into the tree. Given a binary search tree with no duplicates, how many different arrays would result in the creation of this tree. 51 | 52 | */ 53 | 54 | function BinarySearchTree (value) { 55 | this.value = value; 56 | this.left = null; 57 | this.right = null; 58 | } 59 | 60 | BinarySearchTree.prototype.insert = function(value) { 61 | // implement me... 62 | }; 63 | // Time complexity: 64 | 65 | BinarySearchTree.prototype.contains = function(value) { 66 | // implement me... 67 | }; 68 | // Time complexity: 69 | 70 | BinarySearchTree.prototype.traverseDepthFirst_inOrder = function(fn) { 71 | // implement me... 72 | }; 73 | // Time complexity: 74 | 75 | BinarySearchTree.prototype.traverseDepthFirst_preOrder = function(fn) { 76 | // implement me... 77 | }; 78 | // Time complexity: 79 | 80 | BinarySearchTree.prototype.traverseDepthFirst_postOrder = function(fn) { 81 | // implement me... 82 | }; 83 | // Time complexity: 84 | 85 | 86 | BinarySearchTree.prototype.checkIfFull = function() { 87 | // implement me... 88 | }; 89 | // Time complexity: 90 | 91 | BinarySearchTree.prototype.checkIfBalanced = function() { 92 | // implement me... 93 | }; 94 | // Time complexity: 95 | -------------------------------------------------------------------------------- /data-structures/graph.js: -------------------------------------------------------------------------------- 1 | /* 2 | GRAPHS 3 | 4 | Abstract data type 5 | 6 | Basic Graph: 7 | Stores nodes (represented by any primitive value) and the neighbors for each node. This implementation represents a graph as an adjacency list (https://en.wikipedia.org/wiki/Adjacency_list). 8 | 9 | Here's an example: 10 | 1---2---3 11 | \ / 12 | 4 13 | graph = { 14 | 1: [2, 4], 15 | 2: [1, 3, 4], 16 | 3: [2], 17 | 4: [1, 2] 18 | } 19 | 20 | Constraints: 21 | This graph implementation is undirected and can have unconnected nodes. The nodes are represented by unique primitive values. 22 | 23 | *** Operations: 24 | graph.addNode(value) // value must be a primitive 25 | => undefined 26 | Add node to graph 27 | 28 | graph.removeNode(value) 29 | => undefined 30 | Remove node from graph 31 | 32 | graph.contains(value) 33 | => true/false 34 | Returns true if value is found in graph, false otherwise 35 | 36 | graph.addEdge(value1, value2) 37 | => undefined 38 | Create connection between two nodes if they're both present in the graph 39 | 40 | graph.removeEdge(value1, value2) 41 | => undefined 42 | Remove connection between two nodes 43 | 44 | graph.hasEdge(value1, value2) 45 | => true/false 46 | Returns true if edge exists, false otherwise 47 | 48 | graph.forEach(callback) 49 | => undefined 50 | Traverse the graph and invoke the passed callback once for each node. The callback function receives the following for each node: node value, node Neighbors, all nodes. 51 | 52 | Implement traversal methods for depth-first and breadth-first traversal. The methods take a starting node and a callback that gets invoked for each node. The callback should receive two arguments: the node value and the distance (number of edges that separate the node from the starting node). See example usage below. 53 | 54 | graph.traverseDepthFirst(value1, callback) 55 | => undefined 56 | Starting at the node with the value passed in, traverse the graph and invoke the callback for each node in a depth-first fashion. 57 | 58 | graph.traverseBreadthFirst(value, callback) 59 | => undefined 60 | Starting at the node with the value passed in, traverse the graph and invoke the callback for each node in a breadth-first fashion. 61 | 62 | Example Usage: 63 | 1---2---3---5 64 | \ / 65 | 4 66 | { 67 | '1': [ 2, 4 ], 68 | '2': [ 1, 3, 4 ], 69 | '3': [ 2, 5 ], 70 | '4': [ 1, 2 ], 71 | '5': [ 3 ] 72 | } 73 | 74 | var traverseDF = []; 75 | graph.traverseDepthFirst(1, function(val, distance) { traverseDF.push([val, distance]) }); 76 | traverseDF should be [ [ 1, 0 ], [ 2, 1 ], [ 3, 2 ], [ 5, 3 ], [ 4, 2 ] ] 77 | 78 | var traverseBF = []; 79 | graph.traverseBreadthFirst(1, function(val, distance) { traverseBF.push([val, distance]) }); 80 | traverseBF should be [ [ 1, 0 ], [ 2, 1 ], [ 4, 1 ], [ 3, 2 ], [ 5, 3 ] ] 81 | 82 | 83 | *** Additional Exercises: 84 | 85 | Given a directed graph and two nodes in the graph, write a function that indicates whether there is a route between the two nodes. Bonus: rather than returning a boolean, have your function return the shortest distance between the two nodes (the number of edges that separate them). 86 | 87 | */ 88 | 89 | 90 | function Graph () { 91 | this._nodes = {}; 92 | } 93 | 94 | Graph.prototype.addNode = function(value) { 95 | // implement me... 96 | }; 97 | // Time complexity: 98 | 99 | Graph.prototype.removeNode = function(value) { 100 | // implement me... 101 | }; 102 | // Time complexity: 103 | 104 | Graph.prototype.contains = function(value) { 105 | // implement me... 106 | }; 107 | // Time complexity: 108 | 109 | Graph.prototype.addEdge = function(value1, value2) { 110 | // implement me... 111 | }; 112 | // Time complexity: 113 | 114 | Graph.prototype.removeEdge = function(value1, value2) { 115 | // implement me... 116 | }; 117 | // Time complexity: 118 | 119 | Graph.prototype.hasEdge = function(value1, value2) { 120 | // implement me... 121 | }; 122 | // Time complexity: 123 | 124 | Graph.prototype.forEach = function(fn) { 125 | // implement me... 126 | }; 127 | // Time complexity: 128 | 129 | Graph.prototype.traverseDepthFirst = function(value, fn, visited, distance) { 130 | // implement me... 131 | }; 132 | // Time complexity: 133 | 134 | Graph.prototype.traverseBreadthFirst = function(value, fn) { 135 | // implement me... 136 | }; 137 | // Time complexity: 138 | -------------------------------------------------------------------------------- /data-structures/hashTable.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | HASH TABLE 4 | 5 | Collection of key-value pairs. 6 | Map keys to values for efficient lookup. 7 | Use an array as the underlying data structure. 8 | Hash table should have a size - this will be used by the hashing function to determine what index to map the key to. 9 | A hashing function is used to map the key to an integer, which is the index that the value is to be stored at. 10 | Since our hashing function might map multiple keys to the same integer, we have to deal with collisions by creating buckets at each index of the storage array. These buckets can be arrays or linked lists. 11 | 12 | 13 | *** Note: 14 | 15 | ES6 includes a Map data structure. It differs from the JavaScript object because the keys can be any value (not just strings like for objects), there is a size property, and there is a guaranteed order (the insertion order). 16 | 17 | Hash tables are also referred to as hash mapse or dictionaries. 18 | 19 | 20 | *** Operations: 21 | 22 | myMap.set(key, value) 23 | => myMap object 24 | Store the key-value pair in the storage array. 25 | If the key already exists, replace stored value with new value. 26 | Use the hashing function to map the key to an integer and store the value at the corresponding index. 27 | Account for the possibility of collisions. 28 | 29 | myMap.get(key) 30 | => value associated with key, or undefined if none 31 | 32 | myMap.has(key) 33 | => true/false depending on if a value has been associated with the key 34 | 35 | myMap.delete(key) 36 | => true if a value was associated with the key 37 | => false if a value was never associated with the key 38 | Remove any value associated to the key 39 | 40 | myMap.count() 41 | => integer number of key/value pairs in hash table 42 | 43 | myMap.forEach(callbackFn) 44 | => no returned value 45 | Invokes callback function once for each key-value pair in the hash table 46 | 47 | 48 | *** Additional Exercises: 49 | 50 | Resize the hash table: 51 | - if the count becomes greater than 75% of the table size, double the table size and redistribute the key/value pairs 52 | - if the count becomes less than 25% of the table size, cut the table size in half and redistribute the key/value pairs 53 | 54 | 55 | 56 | 57 | 58 | */ 59 | 60 | // Simple hashing function to use in your implementation 61 | function simpleHash(str, tableSize) { 62 | var hash = 0; 63 | for (var i=0; i undefined 20 | Add value to heap according to the shape and heap property 21 | 22 | heap.removeMax() 23 | => max value 24 | Remove the max value from the heap, reorder the heap, and return the max value 25 | 26 | */ 27 | -------------------------------------------------------------------------------- /data-structures/linkedList.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LINKED LIST 4 | 5 | Comprised of nodes that represent a sequence. 6 | Each node is composed of data and a reference/link to the next node. 7 | 8 | 9 | *** Operations: 10 | 11 | ** Part 1 12 | 13 | myList.forEach(callbackFn) 14 | invoke callback function with the value of each node 15 | 16 | myList.print() 17 | => string with all values in list (ex: '0, 1, 2, 3') 18 | 19 | myList.insertAfter(refNode, value) 20 | => new node 21 | insert new node associated with value passed in after refNode 22 | 23 | myList.removeAfter(refNode) 24 | => removed node 25 | remove node after the refNode 26 | 27 | myList.insertHead(value) 28 | => new head 29 | insert new head node at the beginning of the list with the value passed in 30 | 31 | myList.removeHead() 32 | => removed head node 33 | remove the head node of the linked list 34 | 35 | myList.findNode(value) 36 | => first node that has a value matching what was passed in 37 | 38 | 39 | * Optimization: 40 | Say we have a linked list that has 100 items and we want to add an item to the very end. How would you do that with your current implementation? How can you modify the data structure to add an item to the end in constant time? 41 | 42 | myList.appendToTail(value) 43 | => new tail node 44 | add a new tail node at the end of the list with the associated value passed in 45 | 46 | myList.removeTail() 47 | => removed tail node 48 | remove the tail node from the list 49 | 50 | 51 | ** Part 2 52 | 53 | Now let's think about creating insertBefore and removeBefore methods for the nodes in our list. Can you think of an efficient way to do so? 54 | 55 | Think about time complexity. What would it be for your current implementation of a linked list? 56 | 57 | How can we modify our data structures (Node and Linked List classes) so that we can make these O(1) operations? 58 | 59 | Once you've come up with a plan, implement the following methods. 60 | 61 | myList.insertBefore(refNode, value) 62 | => new node inserted 63 | insert new node with associated value before refNode 64 | 65 | myList.removeBefore(refNode) 66 | => removed node 67 | remove node before the refNode passed in 68 | 69 | 70 | *** Additional Exercises: 71 | 72 | Implement a circularly linked list: 73 | https://en.wikipedia.org/wiki/Linked_list#Circularly_linked_list 74 | 75 | Reimplement stack and queue data structures using linked lists. 76 | 77 | 78 | */ 79 | 80 | 81 | // PART 1 82 | 83 | function Node(value) { 84 | this.next = null; 85 | this.value = value; 86 | } 87 | 88 | function LinkedList(headValue) { 89 | if (headValue === undefined) console.log('Must provide value for first node'); 90 | this.head = new Node(headValue); 91 | } 92 | 93 | LinkedList.prototype.forEach = function(callback) { 94 | // implement me... 95 | }; 96 | // Time complexity: 97 | 98 | LinkedList.prototype.print = function() { 99 | // implement me... 100 | }; 101 | // Time complexity: 102 | 103 | LinkedList.prototype.insertAfter = function(node, value) { 104 | // implement me... 105 | }; 106 | // Time complexity: 107 | 108 | LinkedList.prototype.removeAfter = function(node) { 109 | // implement me... 110 | }; 111 | // Time complexity: 112 | 113 | LinkedList.prototype.insertHead = function(value) { 114 | // implement me... 115 | }; 116 | // Time complexity: 117 | 118 | LinkedList.prototype.removeHead = function() { 119 | // implement me... 120 | } 121 | 122 | LinkedList.prototype.findNode = function(value) { 123 | // implement me... 124 | }; 125 | // Time complexity: 126 | 127 | LinkedList.prototype.appendToTail = function(value) { 128 | // implement me... 129 | }; 130 | // Time complexity: 131 | 132 | 133 | // PART 2: 134 | 135 | LinkedList.prototype.insertBefore = function(node, value) { 136 | // implement me... 137 | }; 138 | // Time complexity: 139 | 140 | LinkedList.prototype.removeBefore = function(node) { 141 | // implement me... 142 | }; 143 | // Time complexity: 144 | 145 | 146 | 147 | /* 148 | *** Exercises: 149 | 150 | 1. Implement a stack using a linked list. 151 | 152 | 2. Implement a queue using a linked list. 153 | 154 | 3. Write a method that remove duplicates from an unsorted linked list. What is the time complexity? Re-implement the method without using any additional storage structure (constant space complexity). What is the time complexity? 155 | 156 | 4. Reverse a linked list. Do not use any additional storage structures. 157 | 158 | 5. Find the kth to last element of a singly linked list. 159 | 160 | 6. Detect if a linked list has a loop. 161 | 162 | 7. Check if a linked list is a palindrome. 163 | 164 | 8. Given two linked lists that represent numbers, return a linked list that represents the sum of those numbers: 165 | 4 2 5 (4 -> 2 -> 5) 166 | + 7 3 1 (7 -> 3 -> 1) 167 | -------- 168 | 1 1 5 6 (1 -> 1 -> 5 -> 6) 169 | 170 | */ 171 | -------------------------------------------------------------------------------- /data-structures/queue.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | QUEUE 4 | 5 | Abstract data type 6 | FIFO - First in, first out 7 | Collection of elements with enqueue and dequeue operations. 8 | Note that there is a natural order. Elements are removed in the reverse order of their addition. 9 | 10 | DO NOT use an array and the native push/shift method in your implementation. Use an object as the underlying data structure. 11 | 12 | 13 | *** Operations: 14 | 15 | myQueue.enqueue(value) 16 | => count of queue 17 | add value to collection 18 | 19 | myQueue.dequeue() 20 | => oldest element added collection 21 | Remove item so that it is no longer in collection 22 | 23 | myQueue.peek() 24 | => oldest element added collection 25 | Similiar to dequeue, but do not remove element from collection 26 | 27 | myQueue.count() 28 | => number of elements in queue 29 | 30 | 31 | *** Additional Exercises: 32 | 33 | Modify your queue to take a max capacity and return a string if you try to add an element when there's no more room: 34 | myQueue.enqueue(value) 35 | => "Max capacity already reached. Remove element before adding a new one." 36 | 37 | Create a contains method to check if a value is in the queue: 38 | myQueue.contains('findme') 39 | => true/false 40 | What's the time complexity? 41 | 42 | Create an until method to get the number of dequeues until you get to a certain value: 43 | queue values - (first)2-5-7-3-6-9(last) 44 | myQueue.until(7) 45 | => 3 46 | What's the time complexity? 47 | 48 | 49 | 50 | 51 | */ 52 | 53 | function Queue(capacity) { 54 | // implement me... 55 | } 56 | 57 | Queue.prototype.enqueue = function(value) { 58 | // implement me... 59 | }; 60 | // Time complexity: 61 | 62 | Queue.prototype.dequeue = function() { 63 | // implement me... 64 | }; 65 | // Time complexity: 66 | 67 | Queue.prototype.peek = function() { 68 | // implement me... 69 | }; 70 | 71 | Queue.prototype.count = function() { 72 | // implement me... 73 | }; 74 | // Time complexity: 75 | 76 | 77 | 78 | /* 79 | *** Exercises: 80 | 81 | 1. Implement a queue using two stacks. 82 | 83 | 2. Implement a double-ended queue, with the following methods: enqueueLeft, dequeueLeft, enqueueRight, dequeueRight. 84 | 85 | 3. Given a tree, print out the value of each node in breadth-first order using a queue data structure. 86 | 87 | 88 | */ 89 | -------------------------------------------------------------------------------- /data-structures/set.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SET 4 | 5 | Abstract data type 6 | Stores unique values in no particular order 7 | No mechanism for retrieving elements 8 | Your set should be able to store any JavaScript primitive 9 | 10 | *** Operations: 11 | 12 | mySet.count() 13 | => integer value for the number of values present in set 14 | 15 | mySet.add(value) 16 | => set object 17 | 18 | mySet.delete(value) 19 | => true if value was present and removed 20 | => false if value was not present 21 | 22 | mySet.has(value) 23 | => true/false 24 | 25 | mySet.forEach(callbackFn) 26 | => no return value 27 | calls callbackFn once for each value in the set 28 | 29 | Note: ES6 has a Set data structure as part of the core language. 30 | 31 | *** Additional Exercises: 32 | 33 | Modify your set to take a max capacity and return a string if you try to add an element when there's no more room 34 | mySet.add(value) 35 | => "Max capacity already reached. Remove element before adding a new one." 36 | 37 | Make your set able to take objects, arrays, and functions as values in addition to just primitives. 38 | 39 | 40 | */ 41 | 42 | function Set(capacity) { 43 | // implement me... 44 | } 45 | 46 | Set.prototype.count = function() { 47 | // implement me... 48 | }; 49 | // Time complexity: 50 | 51 | Set.prototype.add = function(value) { 52 | // implement me... 53 | }; 54 | // Time complexity: 55 | 56 | Set.prototype.delete = function(value) { 57 | // implement me... 58 | }; 59 | // Time complexity: 60 | 61 | Set.prototype.has = function(value) { 62 | // implement me... 63 | }; 64 | // Time complexity: 65 | 66 | Set.prototype.forEach = function(callback) { 67 | // implement me... 68 | }; 69 | // Time complexity: 70 | 71 | 72 | /* 73 | *** Exercises: 74 | 75 | 1. Implement the following set theory operations: 76 | 77 | mySet.union(otherSet) 78 | => mySet with added values from otherSet 79 | add any values from otherSet into mySet that are not yet there 80 | ex: {1,2,3} union {2,3,4} => {1,2,3,4} 81 | 82 | mySet.intersection(otherSet) 83 | => mySet with values removed that are not in otherSet 84 | remove values from mySet that are not in otherSet 85 | ex: {1,2,3} intersection {2,3,4} => {2,3} 86 | 87 | mySet.difference(otherSet) 88 | => mySet with values removed that are in otherSet 89 | remove values from mySet that are in otherSet 90 | ex: {1,2,3} difference {2,3,4} => {1} 91 | 92 | mySet.hasSubset(otherSet) 93 | => true/false depending on if otherSet is a subset of mySet 94 | ex: {1,2,3} hasSubset {2,3,4} => false 95 | ex: {1,2,3} hasSubset {2,3} => true 96 | 97 | 98 | 2*. Using a set, create a whitelist filter - given a list of whitelist items and a collection to be filtered, return an array with only the items from the collection that are on the whitelist: 99 | 100 | whitelistFilter(collection , whitelist ) 101 | => filtered collection with only items from white list 102 | 103 | 3*. Now create a blacklist filter. 104 | 105 | * exercises adapted from Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne 106 | 107 | */ 108 | -------------------------------------------------------------------------------- /data-structures/stack.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | STACK 4 | 5 | Abstract data type 6 | LIFO - Last in, first out 7 | Collection of elements with push and pop operations. 8 | Note that there is a natural order. Elements are removed in the reverse order of their addition. 9 | 10 | DO NOT use an array and the native push/pop method in your implementation. That's too easy, yeah? =P 11 | Use an object as the underlying data structure. 12 | 13 | 14 | *** Operations: 15 | 16 | myStack.push(value) 17 | => count of stack 18 | add value to collection 19 | 20 | myStack.pop() 21 | => most recent element added collection 22 | Remove item so that it is no longer in collection 23 | 24 | myStack.peek() 25 | => most recent element added collection 26 | Similiar to pop, but do not remove element from collection 27 | 28 | myStack.count() 29 | => number of elements in stack 30 | 31 | 32 | *** Additional Exercises: 33 | 34 | Modify your stack to take a max capacity and return a string if you try to add an element when there's no more room: 35 | myStack.push(value) 36 | => "Max capacity already reached. Remove element before adding a new one." 37 | 38 | Create a contains method to check if a value is in the stack: 39 | myStack.contains('findme') 40 | => true/false 41 | What's the time complexity? 42 | 43 | Create an until method to get the number of pops until you get to a certain value: 44 | stack values - (first)2-5-7-3-6-9(last) 45 | myStack.until(7) 46 | => 4 47 | What's the time complexity? 48 | 49 | 50 | 51 | */ 52 | 53 | function Stack(capacity) { 54 | // implement me... 55 | } 56 | 57 | Stack.prototype.push = function(value) { 58 | // implement me... 59 | }; 60 | // Time complexity: 61 | 62 | Stack.prototype.pop = function() { 63 | // implement me... 64 | }; 65 | // Time complexity: 66 | 67 | Stack.prototype.peek = function() { 68 | // implement me... 69 | }; 70 | // Time complexity: 71 | 72 | Stack.prototype.count = function() { 73 | // implement me... 74 | }; 75 | // Time complexity: 76 | 77 | 78 | /* 79 | *** Exercises: 80 | 81 | 1. Implement a stack with a min method which returns the minimum element currently in the stack. This method should have O(1) time complexity. Make sure your implementation handles duplicates. 82 | 83 | 2. Sort a stack so that its elements are in ascending order. 84 | 85 | 3. Given a string, determine if the parenthesis in the string are balanced. 86 | Ex: balancedParens( 'sqrt(5*(3+8)/(4-2))' ) => true 87 | Ex: balancedParens( 'Math.min(5,(6-3))(' ) => false 88 | 89 | 4. Towers of Hanoi - https://en.wikipedia.org/wiki/Tower_of_Hanoi 90 | You are given three towers (stacks) and N disks, each of different size. You can move the disks according to three constraints: 91 | 1. only one disk can be moved at a time 92 | 2. when moving a disk, you can only use pop (remove the top element) and push (add to the top of a stack) 93 | 3. no disk can be placed on top of a disk that is smaller than it 94 | The disks begin on tower#1. Write a function that will move the disks from tower#1 to tower#3 in such a way that none of the constraints are violated. 95 | */ 96 | -------------------------------------------------------------------------------- /data-structures/tree.js: -------------------------------------------------------------------------------- 1 | /* 2 | TREES 3 | 4 | Abstract data type 5 | 6 | General Tree: 7 | A tree has a root node. 8 | The root node has 0 or more children. 9 | Each child node has 0 or more children. 10 | (each node in the tree can be seen as a subtree) 11 | 12 | Constraints: 13 | A child has only one parent and the root node has no parent. 14 | Note: A tree is a special type of graph. A tree is a graph without cycles. 15 | 16 | *** Operations: 17 | 18 | tree.addChild(value) 19 | => child node (new tree) 20 | add child to tree/subtree and return child node (which should be a tree instance) 21 | 22 | tree.contains(value) 23 | => true/false 24 | Return true if value is in tree, false if not 25 | 26 | tree.traverseDepthFirst(callback) 27 | => undefined 28 | Invoke the callback for every node in a depth-first order 29 | 30 | tree.traverseBreadthFirst(callback) 31 | => undefined 32 | Invoke the callback for every node in a breadth-first order 33 | 34 | *** Additional Exercises: 35 | Given treeA and treeB, check if treeB is a subtree of treeA (meaning that there exists a node n in treeA such that the subtree of n is identical to treeB). 36 | 37 | Given a dictionary, create a prefix tree (commonly known as a trie) 38 | https://en.wikipedia.org/wiki/Trie 39 | 40 | */ 41 | 42 | function Tree (value) { 43 | // implement me... 44 | } 45 | 46 | Tree.prototype.addChild = function(value) { 47 | // implement me... 48 | }; 49 | // Time complexity: 50 | 51 | 52 | Tree.prototype.contains = function(value) { 53 | // implement me... 54 | }; 55 | // Time complexity: 56 | 57 | 58 | Tree.prototype.traverseDepthFirst = function(fn) { 59 | // implement me... 60 | }; 61 | // Time complexity: 62 | 63 | 64 | Tree.prototype.traverseBreadthFirst = function(fn) { 65 | // implement me... 66 | }; 67 | // Time complexity: 68 | -------------------------------------------------------------------------------- /recursion/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /recursion/factorial.js: -------------------------------------------------------------------------------- 1 | /* 2 | Implement factorial. 3 | 4 | factorial(5) => 5*4*3*2*1 => 120 5 | */ 6 | -------------------------------------------------------------------------------- /recursion/fibonacci.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Write a function that outputs the nth Fibonnaci number. A number in this sequence is found by adding up the two numbers before it. 4 | 5 | Fibonnaci's sequence: 6 | input 0 1 2 3 4 5 6 7 8 9 ... 7 | output 0 1 1 2 3 5 8 13 21 34 ... 8 | 9 | What is the time complexity? Can you think of optimizing your solution? (Hint: look up dynamic programming) 10 | */ 11 | -------------------------------------------------------------------------------- /recursion/flatten.js: -------------------------------------------------------------------------------- 1 | /* 2 | Implement a function that flattens a nested array. 3 | 4 | flatten([1,[2],[3, [[4]]]]); 5 | => [1,2,3,4] 6 | */ 7 | -------------------------------------------------------------------------------- /recursion/greatestCommonDivisor.js: -------------------------------------------------------------------------------- 1 | /* 2 | Write a function that takes two numbers and returns the greatest common divisor. 3 | */ 4 | -------------------------------------------------------------------------------- /recursion/paintFill.js: -------------------------------------------------------------------------------- 1 | /* 2 | Implement a function that takes in a two-dimensional array of colors that represents a screen, a point in the array, and a color. The function will change the original color of the point to the new color and will fill the surrounding area with the original color in the same fashion. 3 | 4 | */ 5 | -------------------------------------------------------------------------------- /recursion/pathCount.js: -------------------------------------------------------------------------------- 1 | /* 2 | Version 1: 3 | Given the size of a grid (X rows and Y columns), write a function that returns the number of possible paths one can take starting at the top left of the grid and ending at the bottom right, assuming you can only move to the right and down. 4 | 5 | Version 2: 6 | Now, imagine that the you can move up, down, left, or right but cannot visit a spot that has already been visited. How many unique paths can the you take? 7 | Hint: it may be useful to create a grid class and use it to keep track of the state as the you traverses the grid. What useful methods can you put on your grid class? Can you write an implementation that only uses a single grid? 8 | */ 9 | -------------------------------------------------------------------------------- /recursion/recursionIntro.js: -------------------------------------------------------------------------------- 1 | //1. Write a function that loops through the numbers n down to 0. If you haven't done so try using a while loop to do this. 2 | 3 | //2. Next, try looping just like above except using recursion 4 | 5 | //3.Write a function 'exponent' that takes two arguments base, and expo, uses a while loop to return the exponenet value of the base. 6 | 7 | //4. Write a function 'RecursiveExponent' that takes two arguments base, and expo, recursively returns exponent value of the base. 8 | 9 | //5. Write a function 'recursiveMultiplier' that takes two arguments, 'arr and num', and multiplies each arr value into by num and returns an array of the values. 10 | 11 | //6. Write a function 'recursiveReverse' that takes an array and uses recursion to return its contents in reverse 12 | -------------------------------------------------------------------------------- /recursion/reverse.js: -------------------------------------------------------------------------------- 1 | /* 2 | Implement a function that will reverse a string recursively. 3 | 4 | reverse('abcdefg') 5 | => 'gfedcba' 6 | */ 7 | -------------------------------------------------------------------------------- /recursion/stringPermutation.js: -------------------------------------------------------------------------------- 1 | /* 2 | Write a function that takes a string and returns all permutations of the string. Ensure that there are no duplicates in the output. 3 | */ 4 | -------------------------------------------------------------------------------- /searching-algorithms/binarySearchArray.js: -------------------------------------------------------------------------------- 1 | /* 2 | BINARY SEARCH ARRAY 3 | 4 | *** Description 5 | 6 | Given a sorted array and a value, determine if the value is in the array using the binary search (divide and conquer) method. 7 | 8 | *** Exercises 9 | 10 | Write a function that takes a sorted array and a value and returns the index of the value in the array. Return null if the value is not found in the array. What is the time complexity? 11 | 12 | Extra credit: Implement the function both iteratively and recursively. 13 | 14 | */ 15 | -------------------------------------------------------------------------------- /sorting-algorithms/bubble.js: -------------------------------------------------------------------------------- 1 | /* 2 | Bubble SORT 3 | 4 | *** Description 5 | 6 | Iterate over array, comparing adjacent items and swap if in incorrect order. Largest elements bubble to the end of the array 7 | 8 | *** Exercises 9 | 10 | - Implement bubble sort 11 | - Identify time complexity 12 | 13 | Optimizations: 14 | - Make algorithm adaptive (if at any point array is already sorted, exit function early). After doing this, what is time complexity for nearly sorted arrays? 15 | - For each pass through the array, are you doing any unnecessary checking of elements? Minimize checking and consider the effect on time complexity. 16 | 17 | Variants: 18 | - Implement cocktail sort (for each pass find both min and max values and sort in both directions). How does this impact performance? 19 | (https://en.wikipedia.org/wiki/Cocktail_sort) 20 | 21 | */ 22 | -------------------------------------------------------------------------------- /sorting-algorithms/heap.js: -------------------------------------------------------------------------------- 1 | /* 2 | HEAP SORT 3 | 4 | *** Description 5 | 6 | Heap sort consists of two parts: 7 | 1) Build a heap out of the data from the input array 8 | 2) Create sorted array by iteratively removing the largest element from the heap and inserting it into the sorted array 9 | 10 | *** Exercise 11 | 12 | Implement heap sort using the Heap constructor provided. 13 | 14 | *** Extra Credit 15 | 16 | Now try building heapsort from scratch, without using the Heap constructor. See if you can do everything in place. 17 | (for hints see https://en.wikipedia.org/wiki/Heapsort#Algorithm) 18 | 19 | Example: 20 | Note that the array is separated into two portions - 21 | heap|sorted 22 | [ 7 2 5 1|8 9 10] 23 | swap first value (max) with element at end of heap 24 | [ 1 2 5 7|8 9 10] 25 | heap size has decreased and sorted portion has now grown 26 | [ 1 2 5|7 8 9 10] 27 | heap portion is re-heapified so that the first value is the new max 28 | [ 5 1 2|7 8 9 10] 29 | heap|sorted 30 | repeat... 31 | 32 | */ 33 | 34 | var Heap = function() { 35 | this.storage = []; 36 | }; 37 | 38 | Heap.prototype.insert = function(value) { 39 | // Push to storage array 40 | this.storage.push(value); 41 | 42 | var that = this; 43 | 44 | // Recursive function to handle swaps, input index 45 | var reheapify = function(index) { 46 | 47 | // Get parent index 48 | var parentInd = Math.ceil(index/2-1); 49 | // Base Case : value < parent or parent is null 50 | if (parentInd < 0 || that.storage[index] <= that.storage[parentInd]) { 51 | return 'value added to index '+index; 52 | } 53 | // Recursive Case: swap with parent and make recursive call 54 | var temp = that.storage[index]; 55 | that.storage[index] = that.storage[parentInd]; 56 | that.storage[parentInd] = temp; 57 | 58 | return reheapify(parentInd); 59 | }; 60 | return reheapify(that.storage.length-1); 61 | }; 62 | 63 | // Heap remove max method on prototype 64 | // Remove the max value from a heap, reorder the heap, and return the max value 65 | Heap.prototype.removeMax = function() { 66 | // Check if heap is currently empty 67 | if (this.storage.length === 0) { 68 | // If nothing to remove then return null 69 | return null; 70 | } else if (this.storage.length === 1) { 71 | // If heap only has one element in it then pop off the lone element in the storage array and return it 72 | var removed = this.storage.pop(); 73 | 74 | return removed; 75 | } 76 | 77 | // Handle all other cases where heap has more than one node 78 | // Preserve the max value in order to return it 79 | var maxValue = this.storage[0]; 80 | // Replace the root node with the last node of the heap and remove the last node 81 | this.storage[0] = this.storage.pop(); 82 | 83 | // Preserve context for inner recursive helper function 84 | var that = this; 85 | 86 | // Recursive function to restore the heap property of the heap 87 | var reheapify = function(index) { 88 | // Set index of max value to current node's index 89 | var maxIndex = index; 90 | 91 | // Check first child node's value against current node 92 | if ((2*index + 1 < that.storage.length) && (that.storage[2*index + 1] > that.storage[index])) { 93 | // If greater then set index of max value to first child node's index 94 | maxIndex = 2*index + 1; 95 | } 96 | // Check second child node's value against current max node 97 | if ((2*index + 2 < that.storage.length) && (that.storage[2*index + 2] > that.storage[maxIndex])) { 98 | // If greater then set index of max value to second child node's index 99 | maxIndex = 2*index + 2; 100 | } 101 | // If the index of the max value is not equal to the index of the current node 102 | // Then swap the nodes and reheapify at the new index of the current node 103 | if (maxIndex !== index) { 104 | // Swap node values (here's a nifty way to do so "in place" using the XOR bitwise operator) 105 | that.storage[index] = that.storage[index] ^ that.storage[maxIndex]; 106 | that.storage[maxIndex] = that.storage[index] ^ that.storage[maxIndex]; 107 | that.storage[index] = that.storage[index] ^ that.storage[maxIndex]; 108 | 109 | // Reheapify at new index of current node 110 | reheapify(maxIndex); 111 | } 112 | }; 113 | 114 | // Recursively move the swapped node down the heap until it's greater than both of its children 115 | reheapify(0); 116 | 117 | // Return the removed max value from the heap 118 | return maxValue; 119 | }; 120 | -------------------------------------------------------------------------------- /sorting-algorithms/insertion.js: -------------------------------------------------------------------------------- 1 | /* 2 | INSERTION SORT 3 | 4 | *** Description 5 | 6 | Iterate over array and grow a sorted array behind current element. 7 | 8 | For each position, compare value of element with previous elements and swap positions if previous element is larger. 9 | 10 | example: 11 | [ 3 4 5|1 2 6 ] 12 | sorted|unsorted 13 | swaps: 14 | [ 3 4 1 5|2 6 ] 15 | [ 3 1 4 5|2 6 ] 16 | [ 1 3 4 5|2 6 ] 17 | now repeat for next unsorted element 18 | 19 | *** Exercises 20 | 21 | - Implement insertion sort for array of numbers 22 | - Identify time complexity 23 | 24 | - Modify function to take comparator function. specify default if not provided (check out native Array.sort comparator function for reference) 25 | - Use your comparator function to verify that your sort is stable by taking input: [{value: 15}, {value: 10, order: 1}, {value: 10, order: 2}] 26 | 27 | *** Extra credit 28 | - Implement shell sort, a generalization of insertion sort 29 | (https://en.wikipedia.org/wiki/Shellsort) 30 | 31 | */ 32 | -------------------------------------------------------------------------------- /sorting-algorithms/merge.js: -------------------------------------------------------------------------------- 1 | /* 2 | MERGE SORT 3 | 4 | *** Description 5 | 6 | Merge sort employs a divide and conquer strategy - merge two sorted subarrays into one sorted array. 7 | 8 | Recursive top-down approach: 9 | Recursively break down array into two subarrays and sort them recursively. Subarrays are broken down until they have only 1 element (implying they are sorted). 10 | 11 | Iterative bottom-up approach: 12 | Split array into sublists of size 1, merge adjacent sublists into sorted lists, repeat until no more sublists. 13 | 14 | *** Exercises 15 | 16 | - Implement recursive merge sort (you might want to write a helper function to handle the merge step) 17 | - Implement iterative merge sort 18 | - Identify time complexity 19 | 20 | - Modify function to take comparator function. specify default if not provided (check out native Array.sort comparator function for reference) 21 | - Use your comparator function to verify that your sort is stable by taking input: [{value: 15}, {value: 10, order: 1}, {value: 10, order: 2}] 22 | 23 | Optimization: 24 | - Refactor your iterative solution to be a natural merge sort. This means that the initial subarrays are naturally occurring sorted sequences. How does this impact time complexity and adaptivity? 25 | ex: 26 | input array: [ 1 2 4 5 9 ] 27 | subarrays for regular merge sort: [ [1], [2], [4], [5], [9] ] 28 | subarrays for natural merge sort: [ [1,2], [4,5], [9] ] 29 | 30 | */ 31 | -------------------------------------------------------------------------------- /sorting-algorithms/quick.js: -------------------------------------------------------------------------------- 1 | /* 2 | QUICK SORT 3 | 4 | *** Description 5 | 6 | Like merge sort, quick sort employs a divide and conquer strategy. 7 | 8 | It has a partitioning step, in which you pick an element (called a pivot) and partition the array so that all smaller elements come before pivot and all larger elements come after. The pivot will be in its final position. Recursively apply this to the subarray of smaller elements and the subarray of larger elements. 9 | 10 | *** Exercises 11 | 12 | - Write a partition helper function. For choice of pivot, for a basic implementation, we recommend choosing either the first or last element in the subarray. If you need hints, look up the Lumoto partiton scheme. Test this out before moving forward! 13 | - Implement quicksort 14 | - Identify time complexity 15 | 16 | - Consider implications for choice of pivot (https://en.wikipedia.org/wiki/Quicksort#Choice_of_pivot) 17 | 18 | *** Extra Credit 19 | 20 | Variants: 21 | - Implement a multi-pivot quicksort (ex: partition into 3 subarrays using 2 pivots) 22 | 23 | */ 24 | -------------------------------------------------------------------------------- /sorting-algorithms/selection.js: -------------------------------------------------------------------------------- 1 | /* 2 | SELECTION SORT 3 | 4 | *** Description 5 | 6 | Iterate over array and grow a sorted array behind current element. 7 | 8 | For each position, find the smallest element in unsorted subarray starting at that position, and swap elements so that smallest element is at the beginning of unsorted subarray. 9 | 10 | example: 11 | [ 1 2 3|9 5 7 4 ] 12 | sorted|unsorted 13 | smallest element in unsorted subarray is 4 14 | swap with element at beggining of unsorted subarray 15 | sorted portion has now grown: 16 | [ 1 2 3 4|5 7 9 ] 17 | 18 | *** Exercises 19 | 20 | - Implement selection sort 21 | - Identify time complexity 22 | 23 | Stable Variant 24 | - Implement as a stable sort - rather than swapping, the minimum value is inserted into the first position and all other items are shifted one to the right. How does this impact performance? 25 | - Modify function to take comparator function. specify default if not provided (check out native Array.sort comparator function for reference) 26 | - Use your comparator function to verify that your sort is stable by taking input: [{value: 15}, {value: 10, order: 1}, {value: 10, order: 2}] 27 | 28 | - Implement selection sort for a linked list (you can use your data structure implemention from earlier in the course). How does this impact performance and stability? 29 | 30 | */ 31 | --------------------------------------------------------------------------------