├── 1-big-o-notation ├── big-o.md └── graph.png ├── 10-stacks-and-queues ├── 1-stacks.md └── 2-queues.md ├── 11-binary-search-trees ├── 1-binary-search-trees.md ├── binary_search_tree.svg ├── binary_tree.svg └── htmltree.png ├── 12-tree-traversal └── 1-tree-traversal.md ├── 13-binary-heaps └── 1-binary-heaps.md ├── 14-hash-tables ├── 1-hash-tables.html └── 1-hash-tables.md ├── 15-graphs ├── 1-graphs.md ├── 1-implementation.md └── graphs.png ├── 2-performance-of-arrays-and-objects └── 1-performance-of-arrays-and-objects.md ├── 3-problem-solving-approach └── 1-problem-solving-approach.md ├── 4-problem-solving-patterns ├── 1-Frequency-Counters.md ├── 2-Multiple-Pointers.md └── 3-Divide-and-Conquer.md ├── 5-recursion ├── 1-recursion.md └── call-stack.png ├── 6-searching ├── 1-linear-search.md ├── 2-binary-search.md └── 3-string-search.md ├── 7-sorting ├── 1-bubble-sort.md ├── 2-selection-sort.md ├── 3-insertion-sort.md ├── 4-merge-sort.md ├── 5-quick-sort.md └── 6-radix-sort.md ├── 8-data-structures └── 1-data-structures.md └── 9-linked-list ├── 1-singly-linked-list.md └── 2-doubly-linked-list.md /1-big-o-notation/big-o.md: -------------------------------------------------------------------------------- 1 | # Big O Notation 2 | 3 | ## What 4 | * Compare the performance of two or more algorithms numerically. 5 | 6 | ## An example 7 | 8 | Consider a problem to find the sum of all numbers upto a given number n; 9 | Here are two ways to do that: 10 | 11 | 1. Using a for loop: 12 | ```javascript 13 | function addToSum(num) { 14 | let sum = 0; 15 | for (let i = 1; i <= num; i++) { 16 | sum += i; 17 | } 18 | return sum; 19 | } 20 | ``` 21 | 2. Using math formula: 22 | ```javascript 23 | function addToSumFast(num) { 24 | return num * (num + 1) / 2; 25 | } 26 | ``` 27 | 28 | If we compare the time required to compute the sum of first billion digits, we find that the second algorithm is about 100 times faster. But time taken for execution is not a very good measure to compare two algorithms as: 29 | 30 | 1. _Time of execution varies_ from machine to machine and even execution to execution on the same machine. 31 | 2. When _comparing two similarly performing algorithms_, the absolute time of execution may not be able to determine the faster algorithm due to lack of precesion. 32 | 33 | ## A Solution: 34 | 35 | Instead of counting time of execution, what if we count the number of operations. But do we mean by an operaion? Operations include the following: 36 | 37 | 1. Mathematical Operations: +, *, -, /, % 38 | 2. Comparisions: ==, <=, >=, === 39 | 3. Assignment: let i = 5, a = 4 40 | 41 | In the math formula version of our code, for any input size of num, we perform 3 operations. On the other hand in the for loop code, the number of operations we perform depends linearly on n (4n + 1). 42 | 43 | So we can say that the number of operations in the for loop algorithm is _proportional to n_ and in the math algorithm is _proportional to a constant_. 44 | 45 | > Project Idea: Make a js tool that takes in a function as input and plots its time vs n curve 46 | 47 | ## Definition: 48 | The Big O notation allows us to formally talk about the performance of an algorithm. 49 | > Big O : The Big O of an algorithm is **O(f(n))** if the number of operations involved in the algorithm is of the order of some multiple of f(n) as n grows. 50 | 51 | Thus the big O of the for loop example above is **O(n)** 52 | and for the math formula is **O(1)** 53 | 54 | ### Thumb Rules: 55 | 1. Constants don't matter: O(4n + 6) => O(n) 56 | 2. Smaller quantities don't matter: O(4n2 + 3n + 2)=> O(n2) 57 | 3. Analyse the trend as n approaches infinity 58 | 59 | --- 60 | 61 | ## Space Complexity 62 | 63 | Time complexity indicates how much time program takes. Similarly, space complexity indicates how much space a program takes. While estimating the time complexity of an algorithm, we considered the number of operations. Here, while calculating the space complexity, we will consider the number of _declarations_: 64 | 65 | 1. Declaring primitive datatypes takes constant space: 66 | ```javascript 67 | // All have O(1) space 68 | let i = 0; 69 | const a = null; 70 | var isHappy = true; 71 | ``` 72 | 2. Strings, Arrays, Sets etc. take linear space proportional to their size: 73 | ```javascript 74 | // All have O(n) space 75 | let arr = [1, 2, 3, 4, 5]; 76 | const word = "Hello World!"; 77 | ``` 78 | 79 | _The same thumb rules apply as the ones for time complexity._ 80 | 81 | ## Examples: 82 | 83 | 1. O(1): 84 | ```javascript 85 | function sum(array) { 86 | let total = 0; //first declaration 87 | for (const num of array) { //second declaration 88 | total += num; 89 | } 90 | return total 91 | } 92 | ``` 93 | Note: In the for of loop, ```num``` is declared only once but assigned multiple number of times 94 | 95 | 2. O(n): 96 | ```javascript 97 | function doubleArray(array){ 98 | let doubleArray = []; 99 | for(const num of array){ 100 | doubleArray.push(2*num); 101 | } 102 | return doubleArray; 103 | } 104 | ``` 105 | Since we declare an array here, the space complexity depends linearly with its length. Hence the space complexity here is O(n) or O(array.length) 106 | 107 | ## Review 108 | Time complexity counts number of *Operations* 109 | Space complexity counts number of *Declarations* 110 | ![](./graph.png) -------------------------------------------------------------------------------- /1-big-o-notation/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godcrampy/js-notes-dsa-colt-steele/6aab4183afd794c1166d763ce73e91e345ee72ab/1-big-o-notation/graph.png -------------------------------------------------------------------------------- /10-stacks-and-queues/1-stacks.md: -------------------------------------------------------------------------------- 1 | # Stacks 2 | 3 | Last in First Out. Used in browser history, call stacks etc. 4 | 5 | ## Complexity 6 | 1. ```push``` and ```pop``` are constant time. (Array implementation is not) 7 | 8 | ## Implementation 9 | 10 | 1. **Using Arrays** 11 | ```javascript 12 | let a = [1, 2, 3]; 13 | a.push(4); 14 | a.pop(); 15 | // 4 16 | ``` 17 | 18 | 2. **Using Classes** 19 | ```javascript 20 | class Node { 21 | constructor(data) { 22 | this.data = data; 23 | this.next = null; 24 | } 25 | } 26 | 27 | class Stack { 28 | constructor() { 29 | this.first = null; 30 | this.last = null; 31 | this.size = 0; 32 | } 33 | push(data) { 34 | let newNode = new Node(data); 35 | if (this.size === 0) { 36 | this.first = newNode; 37 | this.last = newNode; 38 | } else { 39 | newNode.next = this.last; 40 | this.last = newNode; 41 | } 42 | this.size++; 43 | return this.size; 44 | } 45 | pop() { 46 | if (this.size == 0) return undefined; 47 | let temp = this.last; 48 | this.last = this.last.next; 49 | this.size--; 50 | return temp; 51 | } 52 | } 53 | ``` -------------------------------------------------------------------------------- /10-stacks-and-queues/2-queues.md: -------------------------------------------------------------------------------- 1 | # Queues 2 | FIFO. Used in download managers, printing several documents etc. 3 | 4 | ## Complexity 5 | 1. ```queue``` and ```dequeue``` are constant time. (Array implementation is not) 6 | 7 | ## Implementation 8 | 9 | 1. **Using Arrays** 10 | ```javascript 11 | let a = [1, 2, 3]; 12 | a.push(4); 13 | a.shift(); 14 | // 1 15 | ``` 16 | 17 | 2. **Using Classes** 18 | ```javascript 19 | class Node { 20 | constructor(data) { 21 | this.previous = null; 22 | this.data = data; 23 | } 24 | } 25 | 26 | class Queue { 27 | constructor() { 28 | this.first = null; 29 | this.last = null; 30 | this.size = 0; 31 | } 32 | queue(data) { 33 | let newNode = new Node(data); 34 | if (this.size === 0) { 35 | this.first = newNode; 36 | } else this.last.previous = newNode; 37 | this.size++; 38 | this.last = newNode; 39 | return this.size; 40 | } 41 | 42 | dequeue() { 43 | if (this.size == 0) return undefined; 44 | let temp = this.head; 45 | this.head = temp.previous; 46 | this.size--; 47 | return temp; 48 | } 49 | } 50 | ``` -------------------------------------------------------------------------------- /11-binary-search-trees/1-binary-search-trees.md: -------------------------------------------------------------------------------- 1 | # Binary Search Trees 2 | 3 | ## Trees 4 | Trees are a data structure where the nodes are in a parent-child realtionship. They are non-linear unlike arrays and linked lists. Each parent in a tree may have multiple children but each child must have single aprent only. Singly linked list is a special case of tree. Nodes must point at their children only and not their siblings ie must go farther from the root. Root is a node with no parent. There is only one node in a tree. 5 | 6 | ![Tree](./htmltree.png) 7 | 8 | ### Definitions 9 | 1. **Root**: Topmost Node of tree with no parents 10 | 2. **Child**: A node directly connected to a node while moving away from the root node 11 | 3. **Parent**: The node to which child is connected to 12 | 4. **Siblings**: Nodes with same parents 13 | 5. **Leaf**: Nodes with no children 14 | 6. **Edge**: The connection between parent and child node 15 | 16 | ### Use 17 | 1. HTML DOM 18 | 2. Network Structure 19 | 3. Syntax Tree 20 | 4. Directory Structure 21 | 5. JSON 22 | 23 | ## Binary Tree 24 | Subset of trees. Every node can have atmost _two_ children. 25 | 26 | ![Binary Tree](./binary_tree.svg) 27 | 28 | ## Binary Search Tree 29 | Subset of Binary Trees. Every child to the left of a node is smaller than parent node and every child to the right is larger than the parent. 30 | 31 | ![Binary Search Tree](./binary_search_tree.svg) 32 | 33 | ### Complexity 34 | 35 | 1. Searching and Inserting: (Doubling sized increases one step) 36 | * Best: O(1) 37 | * Average: O(log(n)) 38 | * Worst: O(n) 39 | 40 | > Worst occurs if the binary tree is one sided. That is every node has child only on the right(or left) side. In this case, it is basically a linked list. 41 | 42 | 43 | ### Implementation 44 | ```javascript 45 | class Node { 46 | constructor(data) { 47 | this.left = null; 48 | this.right = null; 49 | this.data = data; 50 | } 51 | } 52 | 53 | class BinarySearchTree { 54 | constructor() { 55 | this.root = null; 56 | } 57 | insert(data) { 58 | let newNode = new Node(data) 59 | if (this.root === null) { 60 | this.root = newNode; 61 | return true; 62 | } 63 | let iterator = this.root; 64 | while (true) { 65 | if (data == iterator.data) return false; 66 | if (data > iterator.data) { 67 | // go to the right 68 | if (iterator.right == null) { 69 | iterator.right = newNode; 70 | break; 71 | } 72 | iterator = iterator.right; 73 | } else { 74 | // go to the left 75 | if (iterator.left == null) { 76 | iterator.left = newNode; 77 | break; 78 | } 79 | iterator = iterator.left; 80 | } 81 | } 82 | return true; 83 | } 84 | find(data) { 85 | let iterator = this.root; 86 | while (iterator != null) { 87 | if (data === iterator.data) return true; 88 | else if (data > iterator.data) iterator = iterator.right; 89 | else iterator = iterator.left; 90 | } 91 | return false; 92 | } 93 | } 94 | ``` -------------------------------------------------------------------------------- /11-binary-search-trees/binary_search_tree.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 162 | 164 | 165 | 166 | 167 | 169 | Adobe PDF library 5.00 170 | 171 | 172 | 173 | 174 | 175 | 177 | 2004-01-23T20:04:24-04:00 178 | 2005-12-31T21:30:20Z 179 | Adobe Illustrator 10.0 180 | 2004-01-23T20:25:01-05:00 181 | 182 | 183 | image/svg+xml 186 | 187 | 188 | 189 | 8 212 | 3 235 | 10 258 | 1 281 | 6 304 | 14 327 | 4 350 | 7 373 | 13 396 | -------------------------------------------------------------------------------- /11-binary-search-trees/binary_tree.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2 32 | 33 | 7 49 | 50 | 5 66 | 67 | 2 83 | 84 | 6 100 | 101 | 9 117 | 118 | 5 134 | 135 | 11 151 | 152 | 4 168 | 169 | -------------------------------------------------------------------------------- /11-binary-search-trees/htmltree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godcrampy/js-notes-dsa-colt-steele/6aab4183afd794c1166d763ce73e91e345ee72ab/11-binary-search-trees/htmltree.png -------------------------------------------------------------------------------- /12-tree-traversal/1-tree-traversal.md: -------------------------------------------------------------------------------- 1 | # Tree Traversal 2 | Traversing a tree means visiting every node on the tree once. 3 | 4 | ## Types: 5 | 1. Breadth First Search 6 | 2. Depth First Search 7 | 1. DFS PreOrder 8 | 2. DFS PostOrder 9 | 3. DFS Inorder 10 | 11 | ##BFS: 12 | 13 | To implement BFS, we use queues. We start by adding the root node to the queue. Then as long as the queue is not empty we follow the following algorithm: 14 | 1. Dequeue a node 15 | 2. Add its value to the list 16 | 3. Queue its left and right parameters to the queue 17 | 18 | ```javascript 19 | // Inside Binary Search Classs from previous section 20 | BFS() { 21 | let queue = []; 22 | let list = []; 23 | queue.push(this.root); 24 | while (queue.length != 0) { 25 | let node = queue.shift(); 26 | list.push(node.data); 27 | if(node.left != null)queue.push(node.left) 28 | if(node.right != null)queue.push(node.right) 29 | } 30 | return list 31 | } 32 | ``` 33 | 34 | ## DFS: 35 | 36 | We implement DFS recursively. When we encounter a node, we can do things in three different ways 37 | 38 | 1. PreOrder: 39 | 40 | 1. Add the node value to the list. 41 | 2. Encounter the left node 42 | 3. Encounter the right node 43 | 44 | 2. PostOrder 45 | 46 | 1. Encounter the left node 47 | 2. Encounter the right node 48 | 3. Add the node value to the list 49 | 50 | 3. InOrder 51 | 52 | 1. Encounter the left node 53 | 2. Add the node value to the list 54 | 3. Encounter the right node 55 | 56 | ```javascript 57 | // Inside Binary Search Classs from previous section 58 | DFSPreOrder() { 59 | let list = []; 60 | 61 | function searchRecursive(node) { 62 | list.push(node.data); 63 | if (node.left != null) searchRecursive(node.left) 64 | if (node.right != null) searchRecursive(node.right) 65 | } 66 | searchRecursive(this.root); 67 | return list; 68 | } 69 | DFSPostOrder() { 70 | let list = []; 71 | 72 | function searchRecursive(node) { 73 | if (node.left != null) searchRecursive(node.left) 74 | if (node.right != null) searchRecursive(node.right) 75 | list.push(node.data); 76 | } 77 | searchRecursive(this.root); 78 | return list; 79 | } 80 | DFSInOrder() { 81 | let list = []; 82 | 83 | function searchRecursive(node) { 84 | if (node.left != null) searchRecursive(node.left) 85 | list.push(node.data); 86 | if (node.right != null) searchRecursive(node.right) 87 | } 88 | searchRecursive(this.root); 89 | return list; 90 | } 91 | ``` 92 | 93 | ## BFS vs DFS 94 | Time complexity for all four methods is same as we visit all the nodes once. But BFS takes more memory if we have a really dense tree due to the queue. When traversing, BFS stores the width in the memory whereas DFS stores the depth in the memory. -------------------------------------------------------------------------------- /13-binary-heaps/1-binary-heaps.md: -------------------------------------------------------------------------------- 1 | # Binary Heap 2 | Binary Heaps are Binary Trees with some rules. Binary Heaps are as compact as possible 3 | * **Min Binary Heap**: The parent nodes are always smaller than the child nodes 4 | * **MAx Binary Heap**: The parent nodes are always greater than the child nodes 5 | 6 | ## Representation 7 | Since heaps are as compact as possible, we can expresss them as an array: 8 | ``` 9 | 8 10 | / \ 11 | 7 3 12 | / \ / 13 | 5 6 2 14 | |=> [8, 7, 3, 5, 6, 2] 15 | ``` 16 | Thus the child nodes and parent nodes are related by index as follows: 17 | 1. If parent is at index n: 18 | * left child is at index 2n + 1 19 | * right child is at index 2n + 2 20 | 21 | 2. If child is at index n, parent is at Math.floor((n-1)/2) 22 | 23 | ## Implementation 24 | 1. **Insert**: Used to insert a value to the heap. First the value is added to the array using ```push```. Then as long as the parent node is smaller then the current node, they are swapped with each other. 25 | 2. **Remove**: Used to remove a node from the heap. The node to be removed is swapped with the last node on the array and then removed using ```pop```. Then since a smaller element has risen to the top, this node is trickled down by swapping it with the larger child as long as either of the child has larger value than this node. 26 | ```javascript 27 | class MaxBinaryHeap { 28 | constructor() { 29 | this.values = [] 30 | } 31 | parentIndex(index) { 32 | if (index === 0) return 0; 33 | return Math.floor((index - 1) / 2) 34 | } 35 | parentValue(index) { 36 | return this.values[this.parentIndex(index)]; 37 | } 38 | childIndexLeft(index) { 39 | let final = 2 * index + 1; 40 | if (final >= this.values.length) return index 41 | return final; 42 | } 43 | childIndexRight(index) { 44 | let final = 2 * index + 2; 45 | if (final >= this.values.length) return index 46 | return final; 47 | } 48 | childValueLeft(index) { 49 | return this.values[this.childIndexLeft(index)]; 50 | } 51 | childValueRigth(index) { 52 | return this.values[this.childIndexRight(index)]; 53 | } 54 | insert(value) { 55 | this.values.push(value) 56 | let currentIndex = this.values.length - 1; 57 | while (this.parentValue(currentIndex) < this.values[currentIndex]) { 58 | [this.values[currentIndex], this.values[this.parentIndex(currentIndex)]] = [this.values[this.parentIndex(currentIndex)], this.values[currentIndex]] 59 | currentIndex = this.parentIndex(currentIndex); 60 | } 61 | } 62 | remove(value) { 63 | let currentIndex = this.values.indexOf(value); 64 | // swap with the last 65 | [this.values[currentIndex], this.values[this.values.length - 1]] = [this.values[this.values.length - 1], this.values[currentIndex]]; 66 | this.values.pop(); 67 | while (this.values[currentIndex] < this.childValueLeft(currentIndex) || this.values[currentIndex] < this.childValueRigth(currentIndex)) { 68 | if (this.childValueLeft(currentIndex) > this.childValueRigth(currentIndex)) { 69 | //swap with left 70 | [this.values[currentIndex], this.values[this.childIndexLeft(currentIndex)]] = [this.values[this.childIndexLeft(currentIndex)], this.values[currentIndex]]; 71 | currentIndex = this.childIndexLeft(currentIndex); 72 | } else { 73 | // swap with right guy 74 | [this.values[currentIndex], this.values[this.childIndexRight(currentIndex)]] = [this.values[this.childIndexRight(currentIndex)], this.values[currentIndex]]; 75 | currentIndex = this.childIndexRight(currentIndex); 76 | } 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | ## Complexity 83 | 1. Insertion: log(n) 84 | 2. Removal: log(n) 85 | 3. Search: O(n) 86 | 87 | ## Priority Queues 88 | Priority Queues are datastructures in which the elements with higher priority are sered first. These are used by the Operating System to execute important tasks first. Prioritty Wueues are implemented using heaps since the root node is always the larget value. Thus the taks are queued in a heap and when the root task is performed, it is removed as in above implementation and the next higher values comes to the top and so on. If instead arrays were used, we would take O(n) time to find the max which is slower. 89 | 90 | > Project Ideas: Implement a todo list app using priority queue -------------------------------------------------------------------------------- /14-hash-tables/1-hash-tables.html: -------------------------------------------------------------------------------- 1 | 1-hash-tables

Hash Tables

1059 |

Objects and Maps in JavaScript, Dictionaries in Python, Maps in Dart are all Hash Table implementation.

1060 |

Working

1061 |

Suppose we want to store a key value pair. Arrays are already key value pair implementation where keys are the indices. But suppose we want the key to be strings, in such case we need to use a hash table. 1062 | Consider the following object: {"pink" : "#ff6932", "cyan" : "#00fff"}. 1063 | To store this data we need to use a hash function and an array. Suppose hash("pink") is 25. Thus we will store "#ff6932" at the 25 index of our array. Thus next time whenever we want to access the value of key "pink", we simply have to pass the key thru the hash function and the output index will be used to access the value from array.

1064 |

Hash Function

1065 |

Hash function is a function that takes any data and converts it into a number.

1066 |

Properties of a good hash function: 1067 | 1. Fast (Constant Time) 1068 | 2. Distributes numbers uniformly 1069 | 3. Returns the same index for the same value everytime

1070 |

Here’s a hash function 1071 |

// size is the size of array to be used
1072 | function hash(string, size) {
1073 |     let total = 0;
1074 |     for (char of string) {
1075 |         total += char.charCodeAt(0)
1076 |     }
1077 |     return total % size;
1078 | }
1079 | 
1080 | The problem with above function is a bad hash function as its O(n) and not that random.

1081 |
// removing O(n)
1082 | function hash(string, size) {
1083 |     let total = 0;
1084 |     for (let i = 0; i < Math.min(string.length, 100); i++) {
1085 |         total += string[i].charCodeAt(0)
1086 |     }
1087 |     return total % size;
1088 | }
1089 | 
1090 | 1091 |
 // Increasing randomness
1092 | function hash(string, size) {
1093 |     let total = 0;
1094 |     const PRIME = 1327;
1095 |     for (let i = 0; i < Math.min(string.length, 100); i++) {
1096 |         total = (total * PRIME + string[i].charCodeAt(0)) % size;
1097 |     }
1098 |     return total;
1099 | }
1100 | 
1101 | 1102 |
1103 |

Pro Tip: Use Prime Number length for storing hash tables. https://qr.ae/TWtE8y

1104 |
1105 |

Handling Collisions

1106 |

What if two strings lead to a same hash?, We solve it by the following methods.

1107 |
    1108 |
  1. Separate Chaining: 1109 | Suppose hash("pink") and hash("orange") gives us a hash of 4. This method stores the key and hash in an array which are stored in an array at the specific index.
  2. 1110 |
1111 |

For Example: at index 4, we will have the following array after the above hashing: [["pink", "#ff6932"], ["orange", "#aa6666"]]. Thus we will need to search the above array to get our value.

1112 |
    1113 |
  1. Linear Probing: 1114 | In this method, if an index is occupied, we look at the next unoccupied index and store our data there. So for the above example, if at index 4 we have [["pink", "#ff6932"], we will go to index 5 and if it is empty, we will store ["orange", "#aa6666"]. This method exhausts the hash array quickly.
  2. 1115 |
1116 |

Implementation

1117 |

```javascript

-------------------------------------------------------------------------------- /14-hash-tables/1-hash-tables.md: -------------------------------------------------------------------------------- 1 | # Hash Tables 2 | Objects and Maps in JavaScript, Dictionaries in Python, Maps in Dart are all Hash Table implementation. 3 | 4 | ## Working 5 | Suppose we want to store a key value pair. Arrays are already key value pair implementation where keys are the indices. But suppose we want the key to be strings, in such case we need to use a hash table. 6 | Consider the following object: ```{"pink" : "#ff6932", "cyan" : "#00fff"}```. 7 | To store this data we need to use a hash function and an array. Suppose ```hash("pink")``` is 25. Thus we will store ```"#ff6932"``` at the 25 index of our array. Thus next time whenever we want to access the value of key ```"pink"```, we simply have to pass the key thru the hash function and the output index will be used to access the value from array. 8 | 9 | ## Hash Function 10 | Hash function is a function that takes any data and converts it into a number. 11 | 12 | **Properties of a good hash function:** 13 | 1. Fast (Constant Time) 14 | 2. Distributes numbers uniformly 15 | 3. Returns the same index for the same value everytime 16 | 17 | Here's a hash function 18 | ```javascript 19 | // size is the size of array to be used 20 | function hash(string, size) { 21 | let total = 0; 22 | for (char of string) { 23 | total += char.charCodeAt(0) 24 | } 25 | return total % size; 26 | } 27 | ``` 28 | The problem with above function is a bad hash function as its O(n) and not that random. 29 | 30 | ```javascript 31 | // removing O(n) 32 | function hash(string, size) { 33 | let total = 0; 34 | for (let i = 0; i < Math.min(string.length, 100); i++) { 35 | total += string[i].charCodeAt(0) 36 | } 37 | return total % size; 38 | } 39 | ``` 40 | 41 | ```javascript 42 | // Increasing randomness 43 | function hash(string, size) { 44 | let total = 0; 45 | const PRIME = 1327; 46 | for (let i = 0; i < Math.min(string.length, 100); i++) { 47 | total = (total * PRIME + string[i].charCodeAt(0)) % size; 48 | } 49 | return total; 50 | } 51 | ``` 52 | 53 | > Pro Tip: Use Prime Number length for storing hash tables. https://qr.ae/TWtE8y 54 | 55 | ## Handling Collisions 56 | What if two strings lead to a same hash?, We solve it by the following methods. 57 | 58 | 1. Separate Chaining: 59 | Suppose ```hash("pink")``` and ```hash("orange")``` gives us a hash of 4. This method stores the key and hash in an array which are stored in an array at the specific index. 60 | 61 | For Example: at index 4, we will have the following array after the above hashing: ```[["pink", "#ff6932"], ["orange", "#aa6666"]]```. Thus we will need to search the above array to get our value. 62 | 63 | 2. Linear Probing: 64 | In this method, if an index is occupied, we look at the next unoccupied index and store our data there. So for the above example, if at index 4 we have ```[["pink", "#ff6932"]```, we will go to index 5 and if it is empty, we will store ```["orange", "#aa6666"]```. This method exhausts the hash array quickly. 65 | 66 | ## Implementation 67 | ```javascript 68 | class HashTable { 69 | constructor() { 70 | this.keyMap = new Array(79); 71 | } 72 | _hash(string) { 73 | let total = 0; 74 | const PRIME = 1327; 75 | for (let i = 0; i < Math.min(string.length, 100); i++) { 76 | total = (total * PRIME + string[i].charCodeAt(0)) % 79; 77 | } 78 | return total; 79 | } 80 | 81 | set(key, value) { 82 | let hash = this._hash(key); 83 | if (this.keyMap[hash] == undefined) 84 | this.keyMap[hash] = [ 85 | [key, value] 86 | ]; 87 | else this.keyMap.push([key, value]); 88 | } 89 | 90 | get(key) { 91 | let hash = this._hash(key); 92 | let chain = this.keyMap[hash]; 93 | for (pair of chain) 94 | if (pair[0] === key) return pair[1]; 95 | return undefined; 96 | } 97 | pairs() { 98 | let pairs = []; 99 | for (let i = 0; i < 79; i++) 100 | if (banana.keyMap[i] != undefined) 101 | for (let pair of banana.keyMap[i]) pairs.push(pair) 102 | return pairs; 103 | } 104 | } 105 | ``` 106 | 107 | ## Complexity 108 | * Insertion: O(1) 109 | * Deletion: O(1) 110 | * Access: O(1) -------------------------------------------------------------------------------- /15-graphs/1-graphs.md: -------------------------------------------------------------------------------- 1 | # Graphs 2 | Interconnected Nodes. Superset of trees. 3 | 4 | ![Trees](./graphs.png) 5 | 6 | ## Use 7 | 1. Social Network 8 | 2. Networks 9 | 3. Google Maps 10 | 4. Recommendation Engines 11 | 12 | ## Graphs vs Trees: 13 | In trees, for going from one node to another, there is only one path whereas for graphs, there may be multiple paths. 14 | 15 | ## Terminology 16 | 1. **Vertice**: Nodes of the graph 17 | 2. **Edge**: Connections between nodes 18 | 3. **Directed Graph**: Graph in which we can traverse an edge in only one way. Eg: Instagram Followers Graph 19 | 4. **Undirected Graph**: Graph in which nodes can be traversed in both directions. Eg: Facebook Friends Graph 20 | 5. **Unweighted Graph**: Edges have no value associated with them. 21 | 6. **Weighted Graph**: Edges have some value associated with them. 22 | 23 | ## Representing Graphs 24 | There are two ways of representing graphs: 25 | 26 | 1. Adjacency Matrix: 27 | 28 | Is a matrix of size VxV where V is the number of Vertices. 29 | The matrix for the above graph would be: 30 | ``` 31 | 0 1 2 3 4 32 | ======================= 33 | 0 0 1 0 0 1 34 | 35 | 1 1 0 1 1 1 36 | 37 | 2 0 1 0 1 0 38 | 39 | 3 0 1 1 0 1 40 | 41 | 4 1 1 0 1 0 42 | 43 | ``` 44 | 45 | 2. Adjacency List: 46 | 47 | Graph is stored in a list or hashmap: 48 | ```javascript 49 | [ 50 | [1, 4],//0 51 | [4, 3, 2],//1 52 | [1, 3],//2 53 | [1, 4, 2],//3 54 | [0, 1, 3],//f 55 | ] 56 | 57 | { 58 | 0 : [1, 4], 59 | 1 : [4, 3, 2], 60 | 2 : [1, 3], 61 | 3 : [1, 4, 1], 62 | 4 : [0, 1, 3], 63 | } 64 | ``` 65 | 66 | ## Complexity Comparision 67 | 1. Add Vertex: 68 | 69 | Matrix: O(v2) 70 | List: O(1) 71 | 72 | 2. Add Edge: 73 | 74 | Matrix: O(1) 75 | List: O(1) 76 | 77 | 3. Remove Vertex: 78 | 79 | Matrix: O(v2) 80 | List: O(v + e) 81 | 82 | 4. Remove Edge: 83 | 84 | Matrix: O(1) 85 | List: O(e) 86 | 87 | 5. Query: 88 | 89 | Matrix: O(1) 90 | List: O(v + e) 91 | 92 | 6. Storage: 93 | 94 | Matrix: O(v2) 95 | List: O(v) -------------------------------------------------------------------------------- /15-graphs/1-implementation.md: -------------------------------------------------------------------------------- 1 | # Graph Implementation 2 | 3 | ```javascript 4 | class Graph { 5 | constructor() { 6 | this.adjacencyList = {}; 7 | } 8 | addVertex(vertex) { 9 | if (!this.adjacencyList[vertex]) 10 | this.adjacencyList[vertex] = []; 11 | } 12 | addEdge(vertex1, vertex2) { 13 | if (!this.adjacencyList[vertex1].includes(vertex2)) 14 | this.adjacencyList[vertex1].push(vertex2); 15 | if (!this.adjacencyList[vertex2].includes(vertex1)) 16 | this.adjacencyList[vertex2].push(vertex1); 17 | } 18 | removeEdge(vertex1, vertex2) { 19 | this.adjacencyList[vertex1] = this.adjacencyList[vertex1].filter((value) => value != vertex2); 20 | this.adjacencyList[vertex2] = this.adjacencyList[vertex2].filter((value) => value != vertex1); 21 | } 22 | deleteVertex(vertex) { 23 | let vertices = [...this.adjacencyList[vertex]]; 24 | for (let vertexIndex in vertices) { 25 | // Remove vertex from corresponding vertex array 26 | let otherVertex = vertices[vertexIndex]; 27 | this.adjacencyList[otherVertex] = this.adjacencyList[otherVertex].filter((value) => value != vertex); 28 | // delete vertex itself 29 | delete this.adjacencyList[vertex]; 30 | } 31 | } 32 | DFS(vertex) { 33 | let visited = {}; 34 | let result = []; 35 | const adjacencyList = this.adjacencyList; 36 | 37 | function searchDepthFirst(vertex) { 38 | if (!vertex) return null; 39 | visited[vertex] = true; 40 | result.push(vertex); 41 | adjacencyList[vertex].forEach(function (nextVertex) { 42 | if (!visited[nextVertex]) 43 | return searchDepthFirst(nextVertex); 44 | }) 45 | } 46 | searchDepthFirst(vertex) 47 | return result; 48 | } 49 | BFS(vertex) { 50 | let visited = {}; 51 | let result = []; 52 | let queue = [vertex]; 53 | visited[vertex] = true; 54 | let currentVertex; 55 | while (queue.length) { 56 | currentVertex = queue.shift; 57 | result.push(currentVertex); 58 | 59 | this.adjacencyList[currentVertex].forEach((nextVertex) => { 60 | if (!visited[nextVertex]) { 61 | visited[nextVertex] = true; 62 | queue.push(neighbor); 63 | } 64 | }); 65 | } 66 | return result; 67 | } 68 | } 69 | ``` -------------------------------------------------------------------------------- /15-graphs/graphs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godcrampy/js-notes-dsa-colt-steele/6aab4183afd794c1166d763ce73e91e345ee72ab/15-graphs/graphs.png -------------------------------------------------------------------------------- /2-performance-of-arrays-and-objects/1-performance-of-arrays-and-objects.md: -------------------------------------------------------------------------------- 1 | # Performance of Objects and Arrays 2 | 3 | ## Objects 4 | Given a key, that specific key value pair of the object can be accessed in constant time. 5 | 6 | 1. Searching **O(n)**: Search if a value exists in the object 7 | 2. Accessing **O(1)**: Return the value given a key 8 | 3. Deleting **O(1)**: Removal of key value pair given a key 9 | 4. Insertion **O(1)**: Insertion of a new key value pair 10 | 11 | ### Object Methods 12 | Consider a ```banana``` object: 13 | 14 | ```javascript 15 | let banana = { 16 | color : "yellow", 17 | taste : "sweet", 18 | calories : 100, 19 | isFav : true 20 | } 21 | ``` 22 | 1. Get an array of all the keys **O(n)** 23 | ```javascript 24 | Object.keys(banana); 25 | // ["color", "taste", "calories", "isFav"] 26 | // O(n) as we need to access all the keys 27 | ``` 28 | 2. Get an array of all the values **O(n)** 29 | ```javascript 30 | Object.values(banana); 31 | // ["yellow", "sweet", 100, true] 32 | // O(n) as we need to access all the values 33 | ``` 34 | 3. Get all the key-value pair **O(n)** 35 | ```javascript 36 | Object.entries(banana); 37 | // [["color", "yellow"], ["taste", "sweet"], ["calories", 100], ["isFav", true]] 38 | // O(n) as we need to access all the key-value pairs 39 | ``` 40 | 4. Check if a key exists **O(1)** 41 | ```javascript 42 | banana.hasOwnProperty("color"); 43 | // true 44 | banana.hasOwnProperty("yellow"); 45 | // false 46 | ``` 47 | 48 | ## Arrays 49 | Given an index, the thing at that index can be accessed in constant time. 50 | 51 | 1. Searching **O(n)**: Search if a value exists in the array 52 | 2. Accessing **O(1)**: Return the value at given index 53 | 3. Deleting: 54 | * Delete last element: **O(1)** 55 | * Delete first of middle element: **O(n)** (as it involves reassigning the indices) 56 | 4. Insertion: 57 | * Insert last element: **O(1)** 58 | * Insert first of middle element: **O(n)** (as it involves reassigning the indices) 59 | 60 | ### Array Methods: 61 | Consider the ```groceries``` array: 62 | ```javascript 63 | let groceries = ['milk', 'egg', 'bread', 'bananas']; 64 | ``` 65 | 66 | 1. Insert and Delete at the last index **O(1)**: 67 | ```javascript 68 | groceries.pop(); 69 | // ['milk', 'egg', 'bread'] 70 | groceries.push('apples'); 71 | //['milk', 'egg', 'bread', 'apples'] 72 | ``` 73 | 2. Insert and Delete at the first index **O(n)**: 74 | ```javascript 75 | groceries.shift(); 76 | // [egg', 'bread', 'bananas'] 77 | groceries.unshift('beer'); 78 | //['beer', 'egg', 'bread', 'apples'] 79 | ``` 80 | 3. Insert and Delete at the middle of array **O(n)**: 81 | ```javascript 82 | groceries.splice(1, 1, 'pizza', 'meat'); 83 | // ['milk', 'pizza', 'meat', 'bread', 'bananas'] 84 | ``` 85 | 4. Concatenation of two arrays **O(a + b)**: 86 | ```javascript 87 | let a = [1, 2, 3]; 88 | let b = [4, 5, 6]; 89 | a.concat(b); 90 | // [1, 2, 3, 4, 5, 6] 91 | ``` 92 | 5. Slicing an array **O(n)**: 93 | ```javascript 94 | let a = [1, 2, 3, 4, 5, 6]; 95 | a.slice(2, 4); 96 | // [3, 4] 97 | ``` 98 | 6. Sorting an array **O(nlong(n))**: 99 | ```javascript 100 | let a = [4, 6, 1, 2, 5, 3] 101 | a.sort(); 102 | // [1, 2, 3, 4, 5, 6] 103 | ``` 104 | 7. Iteration over an array **O(n)**: 105 | ```javascript 106 | let a = [1, 2, 3, 4, 5, 6] 107 | a.forEach((value)=>console.log(value)); 108 | // 1 109 | // 2 110 | // 3 111 | // 4 112 | // 5 113 | // 6 114 | a.map((value)=>2*value); 115 | // [2, 4, 6, 8, 10, 12] 116 | 117 | a.filter((value)=>value%2==0); 118 | // [2, 4, 6] 119 | 120 | a.reduce((accumulator, value)=>accumulator + value); 121 | // 21 122 | ``` -------------------------------------------------------------------------------- /3-problem-solving-approach/1-problem-solving-approach.md: -------------------------------------------------------------------------------- 1 | # Problem Solving Approach 2 | 3 | ## Algorithm 4 | A _process_ or _sequence of tasks_ to accomplish a certain task 5 | 6 | ## General Approach 7 | 1. Understand the Problem 8 | 2. Explore Concrete Examples 9 | 3. Break it down 10 | 4. Solve/Simplify 11 | 5. Look Back and Refactor 12 | 13 | ## 1. Understand the problem: 14 | 1. Restate the problem in your own words. 15 | 2. What are the inputs? and their constraints? 16 | 3. What are the outputs? and their constraints? 17 | 4. Do I have enough informantion to determine the ouputs from inputs?(You may not know the answer to this right away!) 18 | 5. How should I label the important pieces of information that are a part of this problem? 19 | 20 | ## 2. Explore Concrete Examples: 21 | 1. Try a simple case 22 | 2. Try a boundary case 23 | 3. Try empty case (inputs not passed into function) 24 | 4. Try invalid cases (strings passed instead of numbers) 25 | 26 | ## 3. Break it Down: 27 | * Break the problem into smaller tasks and write each task as a comment 28 | 29 | ## 4. Solve or Simplify: 30 | * Solve each of the smaller tasks if you can. 31 | * If you can't: 32 | 1. Break down this task into even smaller task or 33 | 2. Try ignoring the difficulty in the problem and solving the problem and then incorporating the difficulty back again 34 | 35 | ## 5. Look Back and Refactor: 36 | 1. See if your solution works on a sample case? 37 | 2. How can this problem be approached differently? 38 | 3. How can every smaller task approached differently? 39 | 4. Is the code understandable in a glance? 40 | 5. Clean the code 41 | 42 | ## Example: 43 | Q: Write a function that takes in a string and returns counts of each charecter in the string as an object 44 | 45 | ### 1. Understand the Question: 46 | 1. Restate the function in your own words: 47 | >Write down a function that takes a string as input, and return an object with the count of each charecter in the string 48 | 49 | 2. What are the inputs? 50 | > The input is a string always 51 | 52 | 3. What are the outputs? 53 | > The output needs to be an object with key of charecters paired up with their count as values 54 | 55 | 4. Are the inputs and outputs related? 56 | > Yes. 57 | 58 | 5. How do I label the important pieces of information in the problem? 59 | > Let's call the input as string and the output Object as table. 60 | 61 | ### 2. Explore Concrete Examples: 62 | 1. Simple 63 | > "Hello" => {h : 1, e : 1, l : 2, o : 1}
64 | > Upper case and lowercase are same
65 | > 0 counts are not returned 66 | 2. Boundary: 67 | > "Hello hi" => {h : 2, e : 1, l : 2, o : 1, i : 1}
68 | > Spaces don't count, only alphanumeric do 69 | 3. Empty: 70 | > "" => {} 71 | 4. Invalid: 72 | > No Invalid Cases will be provided 73 | 74 | ### 3. Break Down the Problem: 75 | ```javascript 76 | function getCountObject(string){ 77 | // define table 78 | // do the counting 79 | //Iterate over the string 80 | //if char is alpa numeric: 81 | // if char in table, value++ 82 | // else add char to the table with value 1 83 | // return table 84 | } 85 | ``` 86 | >TODO: Learn Regex !important 87 | ### 4. Solve or Simplify: 88 | ```javascript 89 | function getCountObject(string){ 90 | // define table 91 | let table = {}; 92 | // do the counting 93 | //Iterate over the string 94 | for(char of table){ 95 | //if char is alpa numeric: 96 | if(isAlphaNumeric(char)){ 97 | // if char in table, value++ 98 | if(table[char]){ 99 | table[char]++; 100 | } 101 | // else add char to the table with value 1 102 | else{ 103 | table[char] = 1; 104 | } 105 | } 106 | } 107 | // return table 108 | return table; 109 | } 110 | function isAlphaNumeric(char){ 111 | if(/[a-z0-9]/.text(char)){ 112 | return true; 113 | } 114 | return false; 115 | } 116 | ``` 117 | 118 | ### 5. Look Back and Refactor 119 | 1. Does it work? 120 | ```javascript 121 | //Corrected Code 122 | function getCountObject(string) { 123 | // define table 124 | let table = {}; 125 | // do the counting 126 | //Iterate over the string 127 | for (char of string) { 128 | char = char.toLowerCase(); 129 | //if char is alpa numeric: 130 | if (isAlphaNumeric(char)) { 131 | // if char in table, value++ 132 | if (table[char]) { 133 | table[char]++; 134 | } 135 | // else add char to the table with value 1 136 | else { 137 | table[char] = 1; 138 | } 139 | } 140 | } 141 | // return table 142 | return table; 143 | } 144 | 145 | function isAlphaNumeric(char) { 146 | if (/[a-z0-9]/.test(char)) { 147 | return true; 148 | } 149 | return false; 150 | } 151 | ``` 152 | 2. How can this problem be approached differently? 153 | > Maybe there is some builtin function which does this 154 | 155 | 3. How can the smaller parts be approached differently? 156 | > 1. lowercase the whole string first 157 | > 2. using ascii values instead of regex 158 | 159 | 4. Is this code understandable at glance? 160 | > can be refactored 161 | 162 | 5. Refactor the code 163 | ```javascript 164 | //Final Code 165 | function getCountObject(string) { 166 | let table = {}; 167 | string.toLowerCase(); 168 | for (char of string) { 169 | if (isAlphaNumeric(char)) { 170 | table[char] = ++table[char] || 1; 171 | } 172 | } 173 | // return table 174 | return table; 175 | } 176 | 177 | function isAlphaNumeric(char) { 178 | return /[a-z0-9]/.test(char); 179 | } 180 | ``` -------------------------------------------------------------------------------- /4-problem-solving-patterns/1-Frequency-Counters.md: -------------------------------------------------------------------------------- 1 | # Frequency Counters 2 | 3 | If there is a loop in loop resulting in a time complexity of _O(n2)_, break down into two seperate for loops by making frequency object structures and then use these structures to do the furthur manipulations as accessing objects is faster than accessing arrays and strings 4 | 5 | Used when there are linear datstructures such as arrays, linked lists and structs and the position of elements is of no use in the problem. 6 | 7 | ## Example 8 | 9 | Write a function same that accepts two arrays. The function should return true if every value in the array has it's corresponding value squared in the second array. The frequency values must be same. 10 | 11 | ### Naive Approach 12 | 13 | 1. Iterate over first loop 14 | 2. For every value in first loop, iterate over the second loop 15 | 3. if the value in second loop is square of value in the first loop, delete that value in the second loop(to account for frequency as well) 16 | 17 | ### Frequency Counter + General Approach from previous chapter 18 | 19 | #### Understand the problem 20 | 21 | 1. Restate the question in own words 22 | > There are two arrays. Return true if for every value the first array, there exists a unique square value in the second array. 23 | 24 | 2. What are the Inputs? 25 | > Two arrays of numbers 26 | 27 | 3. What are the outputs? 28 | > Boolean 29 | 30 | 4. Are Inputs and Outputs related? 31 | > Yes 32 | 33 | 5. What labels should be given for important data 34 | > Let's call the first array as initialArray and the second array as finalArray. Let's call the two tables as initialTable and finalTable. 35 | 36 | #### Explore Concrete Examples 37 | 1. Simple case 38 | > [1, 2, 3], [1, 4, 9] => true]
39 | > Oreder does'nt matter 40 | 41 | 2. Boundary case 42 | > [1, 1, 2], [1, 4, 1] => true
43 | > frequency does matter 44 | 45 | 3. Zero case 46 | > [], [] => true 47 | 48 | 4. Invalid case 49 | > always valid ie arrays 50 | 51 | #### Break Down the Problem 52 | ```javascript 53 | // Use Frequency counter approach to get two arrays first 54 | function same(initialArray, finalArray){ 55 | // make empty tables 56 | // copy array into tables 57 | // compare tables 58 | // return 59 | } 60 | ``` 61 | 62 | #### Solve/Simplify 63 | ```javascript 64 | // Use Frequency counter approach to get two arrays first 65 | function same(initialArray, finalArray){ 66 | // make empty tables 67 | // copy array into tables 68 | let initialTable = arrayToTable(initialArray); 69 | let finalTable = arrayToTable(finalArray); 70 | for(key in initialTable){ 71 | if(initialTable[key] != finalTable[key**2]){ 72 | return false; 73 | } 74 | } 75 | // compare tables 76 | return true; 77 | } 78 | 79 | function arrayToTable(array){ 80 | let table = {}; 81 | //[1, 1, 2, 3] => {1 : 2, 2: 1, 3 : 1} 82 | for(number of array){ 83 | table[number] = ++table[number] || 1; 84 | } 85 | return table; 86 | } 87 | ``` 88 | 89 | #### Look Back and Refactor 90 | 1. Does it run: 91 | > Yes 92 | 2. Ways to approach the problem 93 | > Naive : two for loops 94 | 3. Ways to apporach the parts 95 | > NOt using the second function 96 | 4. Is the code understandable at first glance 97 | > Mostly 98 | 5. Clean 99 | ```javascript 100 | //Final Code 101 | function same(initialArray, finalArray){ 102 | // convert array to frequency tables 103 | let initialTable = arrayToTable(initialArray); 104 | let finalTable = arrayToTable(finalArray); 105 | // compare tables 106 | for(key in initialTable){ 107 | if(initialTable[key] != finalTable[key**2]){ 108 | return false; 109 | } 110 | } 111 | return true; 112 | } 113 | 114 | function arrayToTable(array){ 115 | let table = {}; 116 | //[1, 1, 2, 3] => {1 : 2, 2: 1, 3 : 1} 117 | array.forEach((value)=>table[value] = ++table[value] || 1); 118 | return table; 119 | } 120 | ``` -------------------------------------------------------------------------------- /4-problem-solving-patterns/2-Multiple-Pointers.md: -------------------------------------------------------------------------------- 1 | # Multiple Pointers 2 | 3 | Used when there is a linear **sorted** datastructure and the problem requires comparision between two members. Naive approach would give O(n2), while this method gives O(n) 4 | 5 | This method involves using two pointers to traverse the array. One traverses from the left while the other traverses from the right. 6 | 7 | ## Example: 8 | Write a function sumZero which accepts a sorted array of integers and return the number of pairs whose sum is zero. 9 | 10 | ### Naive Approach 11 | 1. Use a for loop to iterate over the array 12 | 2. For every number, iterate over the array again and find a partner number with sum zero 13 | 14 | > Insight: The informatin that the array is sorted is not used 15 | 16 | ### Multiple Pointers + General Approach 17 | #### Understand the Problem 18 | 1. Restate 19 | > Write a function that takes a sorted array and returns the number of pairs such that the sum of the paired numbers is 0 20 | 2. Input 21 | > a sorted array of numbers 22 | 3. Output 23 | > Number of sumZero pairs 24 | 4. are input and output related 25 | > Yes 26 | 5. Labels for useful fata 27 | > Let's call the input array as simply array and the frequency of pair counter as pairCounter. Let the pointers be called left and righ 28 | 29 | #### Explore Concrete Examples 30 | 1. Simple Case: 31 | > [-3, -2, -1, 0, 1, 2, 3] => 3
32 | > the pointer should move towards each other until they cross each other 33 | 2. Boundary Case: 34 | >[-4, 4, 5,6 , 34, 50] => 1
35 | > the pointer whose absolute value is higher should move 36 | 3. Empty case: 37 | >[] =>0 38 | 4. Invalid Case: 39 | > array assured 40 | 41 | #### Break Down the Problem 42 | ```javascript 43 | function sumZero(array){ 44 | // define pointers and counter 45 | //while pointers dont cross: 46 | // compare numbers 47 | // if pair found move both pointers, counter++ 48 | // else move the pointer with higher value 49 | } 50 | ``` 51 | 52 | #### Solve/Simplify 53 | ```javascript 54 | function sumZero(array){ 55 | // define pointers and counter 56 | let left = 0; 57 | let right = array.length - 1; 58 | let pairCounter = 0; 59 | //while pointers dont cross: 60 | while(left < right){ 61 | // compare numbers 62 | if(array[left] + array[right] === 0){ 63 | // if pair found move both pointers, counter++ 64 | pairCounter++; 65 | left++; 66 | right--; 67 | } 68 | else{ 69 | // else move the pointer with higher value 70 | if(Math.abs(array[left] > Math.abs(right))){ 71 | // left is larger 72 | left++; 73 | } 74 | else{ 75 | right--; 76 | } 77 | } 78 | return pairCounter; 79 | } 80 | ``` 81 | 82 | #### Look Back and Refactor 83 | 1. Does it work? 84 | ```javascript 85 | //Corrected code 86 | function sumZero(array) { 87 | // define pointers and counter 88 | let left = 0; 89 | let right = array.length - 1; 90 | let pairCounter = 0; 91 | //while pointers dont cross: 92 | while (left < right) { 93 | // compare numbers 94 | if (array[left] + array[right] === 0) { 95 | // if pair found move both pointers, counter++ 96 | pairCounter++; 97 | left++; 98 | right--; 99 | } else { 100 | // else move the pointer with higher value 101 | if (Math.abs(array[left]) > Math.abs(array[right])) { 102 | // left is larger 103 | left++; 104 | } else { 105 | right--; 106 | } 107 | } 108 | } 109 | return pairCounter; 110 | } 111 | ``` 112 | 2. Other ways to approach problem? 113 | > Naive way or two for loops 114 | 3. Other ways to approach smaller parts 115 | > Not sure 116 | 4. Is code Readable 117 | > Mostly 118 | 5. Refactor 119 | ```javascript 120 | function sumZero(array) { 121 | let left = 0; 122 | let right = array.length - 1; 123 | let pairCounter = 0; 124 | //while pointers dont cross: 125 | while (left < right) { 126 | // compare numbers 127 | if (array[left] + array[right] === 0) { 128 | // if pair found move both pointers, counter++ 129 | pairCounter++; 130 | left++; 131 | right--; 132 | } else { 133 | // move the pointer with higher value 134 | Math.abs(array[left]) > Math.abs(array[right]) ? left++ : right--; 135 | } 136 | } 137 | return pairCounter; 138 | } 139 | ``` -------------------------------------------------------------------------------- /4-problem-solving-patterns/3-Divide-and-Conquer.md: -------------------------------------------------------------------------------- 1 | # Divide and Conquer 2 | Divide the dataset into smaller parts and reapeating the process for the smaller parts. 3 | Eg: Binary Search, Merge Sort, Quick Sort -------------------------------------------------------------------------------- /5-recursion/1-recursion.md: -------------------------------------------------------------------------------- 1 | # Recursion 2 | A recursive function is one which calls itself while executing. It works on the divide and conquer strategy. Every recursive function must have a base case to end the recursiveness. 3 | 4 | ## Example 5 | **Sum of Numbers** 6 | ```javascript 7 | function sumOfNum(number){ 8 | if(number === 1){ 9 | return 1; 10 | } 11 | return number + sumOfNum(number-1); 12 | } 13 | ``` 14 | 15 | ## Call Stack 16 | Call stack is a stack data structure which handles the function execution in javascript. It works on Last in First Out (LIFO) 17 | 18 | ### Experiment 19 | Try out the following code in the chrome dev console as a snippet and watch the call stack. Add a breakpoint at ```startEngine()``` and then step one by one and observe the call stack. 20 | ```javascript 21 | var ignition = true; 22 | function startEngine(){ 23 | passInFuel(); 24 | passInAir(); 25 | let a = runStarterMotor(); 26 | // let a = runStarterMotor; 27 | // Try both and see their effect on call stack 28 | if(ignition){ 29 | console.log("Engine Started"); 30 | } 31 | else{ 32 | startEngine(); 33 | } 34 | } 35 | 36 | function passInAir(){ 37 | return "Passing in Air"; 38 | } 39 | 40 | function passInFuel(){ 41 | return "Passing in Fuel"; 42 | } 43 | 44 | function runStarterMotor(){ 45 | if(Math.ceil(Math.random()*2) - 1){ 46 | ignition = true; 47 | } 48 | } 49 | 50 | startEngine(); 51 | ``` 52 | ![Call Stack in Chrome Dev Tools](./call-stack.png) 53 | > Note: Call Stacks have a limit to their size which is browser dependent. After the call stack size exceeds the limit, the execition will stop and throw an error. This is known as **Stack Overflow!** 54 | 55 | ## Helper Method Recursion 56 | In this type, recursive method is defined and called inside a main method. 57 | ### Example 58 | Write a function that takes in an array and outputs an array with only odd numbers of the previous array. 59 | 1. With Helper Method Recursion 60 | 61 | ```javascript 62 | function getEven(array){ 63 | let result = []; 64 | function addIfEven(array){ 65 | if(array.length == 0)return; 66 | if(array[0]%2==0)result.push(array[0]); 67 | addIfOdd(array.slice(1)); 68 | } 69 | addIfOdd(array); 70 | return result; 71 | } 72 | ``` 73 | 74 | 2. With Pure Recursion Method 75 | ```javascript 76 | function getEven(array){ 77 | let newArray = [] 78 | if(array.length==0)return []; 79 | if(array[0]%2==0)newArray.push(array[0]); 80 | newArray = newArray.concat(getEven(array.slice(1))); 81 | return newArray; 82 | } 83 | ```` 84 | -------------------------------------------------------------------------------- /5-recursion/call-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godcrampy/js-notes-dsa-colt-steele/6aab4183afd794c1166d763ce73e91e345ee72ab/5-recursion/call-stack.png -------------------------------------------------------------------------------- /6-searching/1-linear-search.md: -------------------------------------------------------------------------------- 1 | # Linear Search 2 | Compares every element one by one till target is reached. 3 | 4 | ## Complexity 5 | 1. Time 6 | 7 | Best: O(1) 8 | 9 | Average: O(n) 10 | 11 | Worst: O(n) 12 | 13 | 2. Space: O(1) 14 | 15 | ## Implementation 16 | ```javascript 17 | function linearSearch(array, target){ 18 | for(index in array){ 19 | if(array[index] == target)return parseInt(index); 20 | } 21 | return -1; 22 | } 23 | ``` 24 | 25 | ## JavaScript inbuilt search functions 26 | ```javascript 27 | let a = [3, 45, 65, 20, 1]; 28 | ``` 29 | **1. find:** 30 | 31 | Takes in a function and return the first value that satisfies the condition in function. The function should return true or false. 32 | 33 | ```javascript 34 | a.find(function(value){ 35 | return value > 40; 36 | }); 37 | //returns 45 38 | 39 | // ES6 Version 40 | a.find((value)=>value > 40); 41 | //returns 45 42 | ``` 43 | 44 | **2. findIndex:** 45 | 46 | Same like find but instead of returning value, it returns index of the values that satisfies the condition 47 | ```javascript 48 | a.findIndex(function(value){ 49 | return value > 40; 50 | }) 51 | // returns 1 52 | 53 | // ES6 version 54 | a.findIndex((value)=> value > 40) 55 | // returns 1 56 | ``` 57 | 58 | **3. indexOf** 59 | 60 | Returns the index of first occurance of the given value 61 | ```javascript 62 | a.indexOf(45) 63 | // returns 1 64 | ``` 65 | 66 | **4. includes** 67 | 68 | Returns tru if the given value exists in the array else returns false 69 | ```javascript 70 | a.includes(45) 71 | // returns true 72 | 73 | a.includes(455) 74 | // returns false 75 | ``` -------------------------------------------------------------------------------- /6-searching/2-binary-search.md: -------------------------------------------------------------------------------- 1 | # Binary Search 2 | 3 | Requires sorted array. Unlike linear search which eliminates one number at a time, binary search eliminates half of the array at a time. Works on _divide and conquer._ 4 | 5 | ## Complexity 6 | 1. Time 7 | 8 | Best: O(1) 9 | 10 | Average: O(log(n)) 11 | 12 | Best: O(log(n)) 13 | 14 | 2. Space: O(1) 15 | 16 | ### Explanation 17 | Suppose we have 16 length array. Is we perform binary search, we half the array overtime repeatedly. So in the worst case 16=>8=>4=>2=>1. Now if we doubled the size of array we will require one extra step. 32=>16=>8=>4=>2=>1. Thus by if complexity is f(n), by doubling n the steps increase by one. Thus f(n) = log(n). Thus O(log(n)) 18 | 19 | ## Implementation 20 | ```javascript 21 | //Using recursion 22 | function binarySearch(array, target, left = 0, right = array.length - 1) { 23 | // find the middle index 24 | let middle = Math.floor((left + right) / 2) 25 | if (array[middle] == target) { 26 | return middle; 27 | } else if (right == left) { 28 | return -1; 29 | } else if (target < array[middle]) { 30 | // target is in the left array 31 | return binarySort(array, target, left, middle - 1); 32 | } else { 33 | // target is in the right array 34 | return binarySort(array, target, middle + 1, right); 35 | } 36 | } 37 | 38 | //Using While Loop 39 | function binarySearch(array, target) { 40 | // Set inital pointers 41 | let left = 0; 42 | let right = array.length; 43 | while (true) { 44 | let middle = Math.floor((left + right) / 2); 45 | if (array[middle] === target) return middle; 46 | if (array.length === 1) return -1; 47 | if (array[middle] < target) { 48 | // target belongs to the right; 49 | left = middle + 1; 50 | } else { 51 | // target belongs to the left 52 | right = middle - 1 53 | } 54 | } 55 | } 56 | ``` -------------------------------------------------------------------------------- /6-searching/3-string-search.md: -------------------------------------------------------------------------------- 1 | # String Search 2 | Given a string and a target string, find if the target string is a substring of the main string. Return the number of times the target string mathes with the substring 3 | 4 | ## Naive Approach 5 | ```javascript 6 | // Loop over both the strings and compare charecter by charecter 7 | function stringSearch(string, target) { 8 | let counter = 0 9 | let isSubstring = false; 10 | for (let i = 0; i < string.length; i++) { 11 | isSubstring = true; 12 | for (let j = 0; j < target.length; j++) { 13 | if (string[i + j] != target[j]) { 14 | isSubstring = false; 15 | break; 16 | } 17 | } 18 | if (isSubstring) counter++; 19 | } 20 | return counter; 21 | } 22 | ``` 23 | 24 | ### Complexity 25 | 1. Time: O(n2) 26 | 2. Space: O(1) -------------------------------------------------------------------------------- /7-sorting/1-bubble-sort.md: -------------------------------------------------------------------------------- 1 | # Bubble Sort 2 | Uses two for loop. In every pass, the largest value of the unsorted array is added to the sorted array. 3 | 4 | ## Complexity 5 | 1. Time 6 | 7 | * Normal Version 8 | * All: O(n2) 9 | 10 | * Optimized Version 11 | * Best: O(n) 12 | * Avg: O(n2) 13 | * Worst: O(n2) 14 | 15 | 16 | 2. Space 17 | * All: O(1) 18 | 19 | ## Implemantation 20 | ```javascript 21 | function bubbleSort(array) { 22 | for (let i = 0; i < array.length; i++) 23 | for (j = array.length - 1; j > i; j--) 24 | if (array[j] < array[j - 1]) 25 | [array[j], array[j - 1]] = [array[j - 1], array[j]]; 26 | // ES6 Swap ^ 27 | } 28 | ``` 29 | 30 | ```javascript 31 | // Optimized 32 | function bubbleSort(array) { 33 | let didDoSwapping = false; 34 | for (let i = 0; i < array.length; i++) { 35 | didDoSwapping = false; 36 | for (j = array.length - 1; j > i; j--) { 37 | if (array[j] < array[j - 1]) { 38 | [array[j], array[j - 1]] = [array[j - 1], array[j]]; 39 | didDoSwapping = true; 40 | } 41 | } 42 | if (!didDoSwapping) break; 43 | } 44 | } 45 | ``` -------------------------------------------------------------------------------- /7-sorting/2-selection-sort.md: -------------------------------------------------------------------------------- 1 | # Selection Sort 2 | We go thru the unsorted array and put the smallest value at the beginning and include it in the sorted array. The only place where selection sort is better than bubble is that it has less number of swaps. 3 | 4 | ## Complexity 5 | 1. Time 6 | * Best: O(n) 7 | * Avg: O(n2) 8 | * Worst: O(n2) 9 | 10 | 11 | 2. Space 12 | * All: O(1) 13 | 14 | ## Implementaion 15 | ```javascript 16 | function selectionSort(array) { 17 | let minPosition; 18 | for (let i = 0; i < array.length - 1; i++) { 19 | //i represents the position to swap with 20 | minPosition = i; 21 | for (let j = i + 1; j < array.length; j++) 22 | if (array[j] < array[minPosition]) 23 | minPosition = j; 24 | [array[i], array[minPosition]] = [array[minPosition], array[i]]; 25 | } 26 | } 27 | ``` -------------------------------------------------------------------------------- /7-sorting/3-insertion-sort.md: -------------------------------------------------------------------------------- 1 | # Insertion Sort 2 | We take one element and insert it in the right place in the sorted part. One advantage over bubble and selection sort is that the it is good when data is getting appended over time. 3 | 4 | ## Complexity 5 | 1. Time 6 | * Best: O(n) 7 | * Avg: O(n2) 8 | * Worst: O(n2) 9 | 10 | 11 | 2. Space 12 | * All: O(1) 13 | 14 | ## Implementation 15 | ```javascript 16 | function insertionSort(array) { 17 | for (let i = 0; i < array.length - 1; i++) 18 | for (let j = i + 1; j > 0; j--) 19 | if (array[j] < array[j - 1])[array[j - 1], array[j]] = [array[j], array[j - 1]]; 20 | else break; 21 | } 22 | ``` -------------------------------------------------------------------------------- /7-sorting/4-merge-sort.md: -------------------------------------------------------------------------------- 1 | # Merge Sort 2 | Works on divide and conquer algorithm. General strategy is to keep on splitting the arrays into halves then merging the halves one by one. 3 | 4 | ## Complexity 5 | 1. Time 6 | * All: O(nlog(n)) 7 | > Suppose some set of data takes time t. Now if we double the data, we have to make one more splitting step. This is log(n) pattern. But for this additional step we have to make n comparisions as well in the merging step. Thus we get O(nlog(n)) 8 | 9 | 2. Space 10 | * All : O(n) 11 | > More double length of left and right arrays created as n doubles 12 | 13 | > Coding Pattern: Avoid while true with breaks inside as it makes the code harder to understand. 14 | 15 | ### Implementation 16 | ```javascript 17 | function mergeSort(array) { 18 | if (array.length <= 1) return array; 19 | let midpoint = Math.floor(array.length / 2) 20 | let left = mergeSort(array.slice(0, midpoint)); 21 | let right = mergeSort(array.slice(midpoint)); 22 | return mergeArrays(left, right); 23 | } 24 | 25 | function mergeArrays(array1, array2) { 26 | // Given two functions, this function constructs a single sorted array 27 | let sortedArray = [], 28 | pointer1 = 0, 29 | pointer2 = 0; 30 | while (pointer1 != array1.length && pointer2 != array2.length) 31 | if (array1[pointer1] > array2[pointer2]) { 32 | sortedArray.push(array2[pointer2]); 33 | pointer2++; 34 | } else { 35 | sortedArray.push(array1[pointer1]); 36 | pointer1++; 37 | } 38 | if (pointer1 == array1.length) 39 | sortedArray = sortedArray.concat(array2.slice(pointer2)); 40 | else 41 | sortedArray = sortedArray.concat(array1.slice(pointer1)); 42 | return sortedArray 43 | } 44 | ``` -------------------------------------------------------------------------------- /7-sorting/5-quick-sort.md: -------------------------------------------------------------------------------- 1 | # Quick Sort 2 | Sorts elements one by one by placing a pivot element in its correct place and then repeating for the left and right elements recursively. 3 | 4 | ## Complexity 5 | 1. Time 6 | 7 | * Best, Avg: O(nlog(n)) 8 | * Worst: O(n2) 9 | 10 | > If certain data takes time t and we double the data, we need to make one morebreaking od array into left and right and for that one extra array we need to compare n times. Thus O(nlog(n)). Worst case comes when the array is already sorted. The pivot will be at its position and we will make n comparisions to move to the next postion which will be at its postion as well and so on for n times. Thus we get O(n2). I way to avoid this is by using ```isSorted(array)``` which checks if an array is sorted. 11 | 12 | 2. Space 13 | 14 | * All: O(log(n)) 15 | 16 | > When array size is doubled, call stack increases by one (Not satisfied with the answer coz this reasoning fails at merge sort) 17 | 18 | ## Implementation 19 | ```javascript 20 | function quickSort(array) { 21 | if (array.length <= 1) return array; 22 | let pivot = array[0]; 23 | let pivotPosition = 0; 24 | for (let i = 1; i < array.length; i++) 25 | if (array[i] < pivot) { 26 | pivotPosition++; 27 | [array[pivotPosition], array[i]] = [array[i], array[pivotPosition]]; 28 | } 29 | [array[0], array[pivotPosition]] = [array[pivotPosition], array[0]]; 30 | let left = quickSort(array.slice(0, pivotPosition)); 31 | let right = quickSort(array.slice(pivotPosition + 1)); 32 | return [...left, pivot, ...right]; 33 | } 34 | ``` -------------------------------------------------------------------------------- /7-sorting/6-radix-sort.md: -------------------------------------------------------------------------------- 1 | # Radix Sort 2 | 3 | Radix sort doesn't use comparisons for sorting instead uses properties of numbers. This sort can be done on integers only. The algorithm groups numbers according to the numbers at units, tens, hundreds and so on places. 4 | 5 | ### Note on JavaScript Methods 6 | >NOTE: primitive type variables like strings and numbers are always passed by value. 7 | Arrays and Objects are passed by reference or by value based on these conditions: 8 | > 1. if you are setting the value of an object or array it is Pass by Value. 9 | 10 | ```javascript 11 | object1 = {prop: "car"}; 12 | array1 = [1,2,3]; 13 | ``` 14 | 15 | >2. if you are changing a property value of an object or array then it is Pass by Reference. 16 | 17 | ```javascript 18 | object1.prop = "car"; 19 | array1[0] = 9; 20 | ``` 21 | ```javascript 22 | function passVar(obj1, obj2, num) { 23 | obj1.prop = "laptop"; // will CHANGE original 24 | obj2 = { prop: "computer" }; //will NOT affect original 25 | num = num + 1; // will NOT affect original 26 | } 27 | 28 | var object1 = { 29 | prop: "car" 30 | }; 31 | var object2 = { 32 | prop: "bike" 33 | }; 34 | var number1 = 10; 35 | 36 | passVar(object1, object2, number1); 37 | console.log(object1); //output: Object {item:"laptop"} 38 | console.log(object2); //output: Object {item:"bike"} 39 | console.log(number1); //ouput: 10 40 | ``` 41 | 42 | ## Complexity 43 | > k is the base on numbers (10 in our case) 44 | 1. Time: O(kn) 45 | 2. Space: O(k + n) 46 | 47 | ## Implementation 48 | ```javascript 49 | function getDigit(number, place) { 50 | // returns the number at the given place 124, 3 => 1 51 | return Math.floor(Math.abs(number) / Math.pow(10, place)) % 10; 52 | } 53 | 54 | function digitCount(number) { 55 | if (number == 0) return 1; 56 | return Math.floor(Math.log10(Math.abs(number))) + 1; 57 | } 58 | 59 | function mostDigits(array) { 60 | let digits = 0; 61 | for (number of array) 62 | digits = Math.max(digitCount(number), number); 63 | return digits; 64 | } 65 | 66 | function radixSort(array) { 67 | let iterations = mostDigits(array); 68 | for (let i = 0; i < iterations; i++) { 69 | let bucket = Array.from({ 70 | length: 10 71 | }, () => []); 72 | for (number of array) 73 | bucket[getDigit(number, i)].push(number); 74 | array = [].concat(...bucket) 75 | } 76 | return array; 77 | } 78 | ``` -------------------------------------------------------------------------------- /8-data-structures/1-data-structures.md: -------------------------------------------------------------------------------- 1 | # Data Structures 2 | Data structures are collection of values, the relationship between them and the functions or the operations that can be applied to the data. 3 | 4 | ## ES2015 Classes 5 | ```javascript 6 | class Student { 7 | static count = 0; 8 | // ES6 Does'nt support static properties :(.Only Chrome console does 9 | constructor(firstname, lastname, year) { 10 | this.firstname = firstname; 11 | this.lastname = lastname; 12 | this.grade = year; 13 | this.scores = []; 14 | Student.count++; 15 | } 16 | fullName() { 17 | return `${this.firstname} ${this.lastname}`; 18 | } 19 | addScore(score) { 20 | this.scores.push(score); 21 | } 22 | averageScore() { 23 | let sum = this.scores.reduce((acc, val) => acc + val); 24 | return sum / this.scores.length; 25 | } 26 | } 27 | 28 | let emily = new Student("Emily", "Wolowitz"); 29 | // grade remains undefined as we don't provide it 30 | 31 | emily.fullName() 32 | // Emily Wolowitz 33 | 34 | emily.addScore(92); 35 | emily.addScore(90); 36 | emily.scores; 37 | // [92, 91] 38 | 39 | emily.averageScore(); 40 | // 91 41 | ``` -------------------------------------------------------------------------------- /9-linked-list/1-singly-linked-list.md: -------------------------------------------------------------------------------- 1 | # Singly Linked List 2 | Collection of nodes each node containing a value and pointer to the next node. 3 | 4 | >In JavaScript, ```undefined``` means a variable has been declared but has not yet been assigned a value.
5 | ```null``` is an assignment value. It can be assigned to a variable as a representation of no value 6 | 7 | >Javascript is garbage collected, you don't need to delete objects themselves - they will be removed when there is no way to refer to them anymore. 8 | 9 | ## Complexity 10 | 1. Search: O(n) 11 | 2. Access: O(n) 12 | 3. Delete: O(n) or O(1) 13 | 4. Insert: O(n) or O(1) 14 | 15 | > Singly Linked List are better than arrays when insertion and deletion at the end and at the start is required. Accessing is difficult in linked lists 16 | 17 | ## Methods 18 | 1. ```push(data)```: Works same as ```push``` of array 19 | 2. ```pop()```: Works same as ```pop``` of array 20 | 3. ```shift()```: Works same as ```shift``` of array 21 | 4. ```unshift(data)```: Works same as ```unshift``` of array 22 | 5. ```get(index)```: Gets the node at given index 23 | 6. ```set(index)```: Sets the node data at given index 24 | 7. ```insert(index, data)```: Inserts a node with given data at the given index 25 | 8. ```remove(index)```: Removes the node at the index 26 | 9. ```reverse()```: Reverses the list 27 | 10. ```traverse()```: Logs the list on the console 28 | 29 | 30 | ## Implementation 31 | ```javascript 32 | class Node { 33 | constructor(data) { 34 | this.data = data; 35 | this.next = null; 36 | } 37 | } 38 | 39 | class SinglyLinkedList { 40 | constructor() { 41 | this.length = null; 42 | this.head = null; 43 | this.tail = null; 44 | } 45 | 46 | push(data) { 47 | let newNode = new Node(data); 48 | if (!this.head) { 49 | // Empty list 50 | this.head = newNode; 51 | this.tail = newNode; 52 | } else { 53 | this.tail.next = newNode; 54 | this.tail = newNode; 55 | } 56 | this.length++; 57 | return this.length; 58 | } 59 | pop() { 60 | if (!this.length) return undefined; 61 | // Find the second last node 62 | let iterator = this.head; 63 | while (iterator.next != this.tail) { 64 | iterator = iterator.next; 65 | } 66 | let final = this.tail.data; 67 | this.tail = iterator; 68 | this.tail.next = null; 69 | this.length--; 70 | if (this.length === 0) { 71 | this.head = null; 72 | this.tail = null; 73 | } 74 | return final 75 | } 76 | shift() { 77 | if (!this.length) return undefined; 78 | let newHead = this.head.next; 79 | let final = this.head.data; 80 | this.head = newHead; 81 | this.length--; 82 | if (this.length === 0) { 83 | this.head = null; 84 | this.tail = null; 85 | } 86 | return final; 87 | } 88 | unshift(data) { 89 | let newHead = new Node(data) 90 | if (this.length === 0) { 91 | this.head = newHead; 92 | this.tail = newHead; 93 | } else { 94 | newHead.next = this.head; 95 | this.head = newHead; 96 | } 97 | this.length++; 98 | return this.length; 99 | } 100 | traverse() { 101 | let iterator = this.head; 102 | while (iterator) { 103 | console.log(iterator.data); 104 | iterator = iterator.next; 105 | } 106 | } 107 | get(index = 0) { 108 | let iteratorPostion = 0; 109 | let iterator = this.head; 110 | if (index >= this.length || index < 0) return null 111 | while (iteratorPostion != index) { 112 | iterator = iterator.next; 113 | iteratorPostion++; 114 | } 115 | return iterator; 116 | } 117 | set(index = 0, data = undefined) { 118 | let iterator = this.get(index); 119 | if (!iterator) return false; 120 | iterator.data = data; 121 | return true; 122 | } 123 | insert(index = 0, data = undefined) { 124 | let prevNode = this.get(index - 1); 125 | let nextNode = this.get(index); 126 | let newNode = new Node(data); 127 | if (this.length === 0 || !nextNode) this.push(data) 128 | else if (!prevNode) this.unshift(data) 129 | else { 130 | prevNode.next = newNode; 131 | newNode.next = nextNode; 132 | this.length++; 133 | } 134 | return this.length; 135 | } 136 | remove(index = 0) { 137 | let prevNode = this.get(index - 1); 138 | let nextNode = this.get(index + 1); 139 | let currentNode = this.get(index); 140 | if (this.length === 0) return null; 141 | else if (!prevNode) this.shift() 142 | else if (!nextNode) this.pop() 143 | else { 144 | prevNode.next = nextNode; 145 | this.length--; 146 | return currentNode.data; 147 | } 148 | } 149 | reverse() { 150 | // flip head and tail 151 | let temp = this.head; 152 | this.head = this.tail; 153 | this.tail = temp; 154 | let node = this.tail; 155 | let prevNode = null; 156 | let nextNode = node.next; 157 | while (node != null) { 158 | node.next = prevNode; 159 | prevNode = node; 160 | node = nextNode; 161 | if (node != null) nextNode = node.next; 162 | } 163 | } 164 | } 165 | ``` -------------------------------------------------------------------------------- /9-linked-list/2-doubly-linked-list.md: -------------------------------------------------------------------------------- 1 | # Double Linked List 2 | Collection of nodes each node containing a value and pointer to the next and the previous node. 3 | 4 | > If a parameter is not passed to a function, that parameter gets the value of undefined. 5 | >https://www.freecodecamp.org/news/copying-stuff-in-javascript-how-to-differentiate-between-deep-and-shallow-copies-b6d8c1ef09cd/ 6 | 7 | ## Complexity 8 | 1. Search: O(n) 9 | 2. Access: O(n) 10 | 3. Delete: O(n) or O(1) 11 | 4. Insert: O(n) or O(1) 12 | 13 | > Faster than or same speed as singly linked list 14 | 15 | ## Methods 16 | 1. ```push(data)```: Works same as ```push``` of array 17 | 2. ```pop()```: Works same as ```pop``` of array 18 | 3. ```shift()```: Works same as ```shift``` of array 19 | 4. ```unshift(data)```: Works same as ```unshift``` of array 20 | 5. ```get(index)```: Gets the node at given index 21 | 6. ```set(index)```: Sets the node data at given index 22 | 7. ```insert(index, data)```: Inserts a node with given data at the given index 23 | 8. ```remove(index)```: Removes the node at the index 24 | 9. ```reverse()```: Reverses the list 25 | 10. ```traverse()```: Logs the list on the console 26 | 27 | ## Implemetation 28 | ```javascript 29 | class Node { 30 | constructor(data) { 31 | this.data = data; 32 | this.previous = null; 33 | this.next = null; 34 | 35 | } 36 | } 37 | 38 | class DoublyLinkedList { 39 | constructor() { 40 | this.head = null; 41 | this.tail = null; 42 | this.length = 0; 43 | } 44 | 45 | push(data) { 46 | let newNode = new Node(data); 47 | if (this.length === 0) { 48 | this.head = newNode; 49 | } else { 50 | this.tail.next = newNode; 51 | newNode.previous = this.tail; 52 | } 53 | this.tail = newNode; 54 | this.length++; 55 | return this.length; 56 | } 57 | pop() { 58 | let final; 59 | if (this.length === 0) final = null; 60 | else if (this.length === 1) { 61 | final = this.head.data; 62 | this.head = null; 63 | this.tail = null; 64 | this.length = 0; 65 | } else { 66 | final = this.tail.data; 67 | this.tail = this.tail.previous; 68 | this.tail.next = null; 69 | this.length--; 70 | } 71 | return final; 72 | } 73 | shift() { 74 | let final; 75 | if (this.length === 0) final = null; 76 | else if (this.length === 1) { 77 | final = this.head.data; 78 | this.head = null; 79 | this.tail = null; 80 | this.length = 0; 81 | } else { 82 | final = this.head.data; 83 | this.head = this.head.next; 84 | this.head.previous = null; 85 | this.length-- 86 | } 87 | return final; 88 | } 89 | unshift(data) { 90 | let newNode = new Node(data); 91 | if (this.length === 0) { 92 | this.tail = newNode; 93 | } else { 94 | this.head.previous = newNode; 95 | newNode.next = this.head; 96 | } 97 | this.head = newNode; 98 | this.length++; 99 | return this.length; 100 | } 101 | get(index) { 102 | if (index < 0 || index >= this.length) return null; 103 | let iterator; 104 | if (index > this.length / 2) { 105 | // Traverse from behind 106 | iterator = this.tail; 107 | let iteratorPosition = this.length - 1; 108 | while (iteratorPosition != index) { 109 | iterator = iterator.previous; 110 | iteratorPosition--; 111 | } 112 | } else { 113 | //travers from front 114 | iterator = this.head; 115 | let iteratorPosition = 0; 116 | while (iteratorPosition != index) { 117 | iterator = iterator.next; 118 | iteratorPosition++; 119 | } 120 | } 121 | return iterator; 122 | } 123 | set(index, data) { 124 | let iterator = this.get(index); 125 | if (!iterator) return false; 126 | iterator.data = data; 127 | return true; 128 | } 129 | insert(index, data) { 130 | let previousNode = this.get(index - 1); 131 | let nextNode = this.get(index); 132 | let newNode = new Node(data); 133 | if (index === 0 || this.length === 0) this.unshift(data) 134 | else if (index === this.length) this.push(data); 135 | else { 136 | previousNode.next = newNode; 137 | newNode.previous = previousNode; 138 | newNode.next = nextNode; 139 | nextNode.previous = newNode; 140 | this.length++; 141 | } 142 | return this.length; 143 | } 144 | remove(index) { 145 | if (index < 0 || index >= this.length) return undefined; 146 | if (index === 0) this.shift() 147 | else if (index === this.length - 1) this.pop() 148 | else { 149 | let final = this.get(index).data; 150 | let previousNode = this.get(index - 1); 151 | let nextNode = this.get(index + 1); 152 | previousNode.next = nextNode; 153 | nextNode.previous = previousNode; 154 | this.length--; 155 | return final; 156 | } 157 | } 158 | reverse() { 159 | let iterator = this.head; 160 | while(iterator){ 161 | let temp = iterator.next; 162 | iterator.next = iterator.previous; 163 | iterator.previous = temp; 164 | iterator = iterator.previous; 165 | } 166 | let temp = this.head; 167 | this.head = this.tail; 168 | this.tail = temp; 169 | } 170 | traverse() { 171 | let iterator = this.head; 172 | while (iterator) { 173 | console.log(iterator.data); 174 | iterator = iterator.next; 175 | } 176 | return this.length; 177 | } 178 | } 179 | ``` --------------------------------------------------------------------------------