├── 01.problemSolving.js ├── 02.recursion.js ├── 03.searching.js ├── 04.bubbleSort.js ├── 05.selectionSort.js ├── 06.insertionSort.js ├── 07.mergeSort.js ├── 08.quickSort.js ├── 09.radixSort.js ├── 10.Arrays.js ├── 11.SinglyLinkedLists.js ├── 12.DoublyLinkedLists.js ├── 13.Stacks.js ├── 14.Queue.js ├── 15.BST.js ├── 16.BinaryHeap.js ├── 17.PriorityQueue.js ├── 18.HashTable.js ├── 19.Graphs.js ├── 20.DijkstrasAlgorithm.js ├── 21.Bellman-FordAlgorithm.js ├── 22.TopologicalSort.js ├── 23.2dArray.js ├── 24.Trie.js ├── 25.DynamicProgramming.js ├── 26.heapSort.js ├── 27.LRUCache.js ├── 28.FloydWarshallAlgorithm.js ├── 29.PrimsAlgorithm.js ├── 30.DisJointSet.js ├── 31.Kruskal'sAlgorithm.js ├── 32.Kosaraju'sAlgorithm.js ├── LICENSE └── README.md /01.problemSolving.js: -------------------------------------------------------------------------------- 1 | // 1. Understand the problem 2 | // 2. Explore concrete examples 3 | // 3. Break it down 4 | // 4. Solve / simplify 5 | // 5. Look back and refactor 6 | 7 | // 1. Frequency Counter Pattern 8 | // This pattern uses objects or sets to collect values/frequencies of values 9 | // This can often avoid the need for nested loops or O(N^2) operations with arrays / strings 10 | 11 | // naive approach, O(n^2) 12 | function same(arr1, arr2) { 13 | if (arr1.length !== arr2.length) { 14 | return false; 15 | } 16 | for (let i = 0; i < arr1.length; i++) { 17 | let correctIndex = arr2.indexOf(arr1[i] ** 2); 18 | if (correctIndex === -1) { 19 | return false; 20 | } 21 | console.log(arr2); 22 | arr2.splice(correctIndex, 1); 23 | } 24 | return true; 25 | } 26 | 27 | same([1, 2, 3, 2], [9, 1, 4, 4]); 28 | 29 | // frequency counter pattern, O(n) 30 | function same2(arr1, arr2) { 31 | if (arr1.length !== arr2.length) { 32 | return false; 33 | } 34 | let frequencyCounter1 = {}; 35 | let frequencyCounter2 = {}; 36 | 37 | for (let val of arr1) { 38 | frequencyCounter1[val] = (frequencyCounter1[val] || 0) + 1; 39 | } 40 | 41 | for (let val of arr2) { 42 | frequencyCounter2[val] = (frequencyCounter2[val] || 0) + 1; 43 | } 44 | 45 | console.log(frequencyCounter1); 46 | console.log(frequencyCounter2); 47 | 48 | for (let key in frequencyCounter1) { 49 | if (!(key ** 2 in frequencyCounter2)) { 50 | return false; 51 | } 52 | if (frequencyCounter2[key ** 2] !== frequencyCounter1[key]) { 53 | return false; 54 | } 55 | } 56 | return true; 57 | } 58 | 59 | same2([1, 2, 3, 2, 5], [9, 1, 4, 4, 11]); 60 | 61 | // 2. Multiple Pointers pattern 62 | 63 | // Creating pointers or values that correspond to an index or position and move towards the beginning, 64 | // end or middle based on a certain condition 65 | // Very efficient for solving problems with minimal space complexity as well 66 | 67 | // naive solution, O(n^2) 68 | function sumZero(arr) { 69 | // sorted array 70 | for (let i = 0; i < arr.length; i++) { 71 | for (let j = i + 1; j < arr.length; j++) { 72 | if (arr[i] + arr[j] === 0) { 73 | return [arr[i], arr[j]]; 74 | } 75 | } 76 | } 77 | } 78 | 79 | sumZero([-4, -3, -2, -1, 0, 1, 2, 5]); // [-2, 2] 80 | 81 | // Multiple pointer pattern, O(n) 82 | function sumZero2(arr) { 83 | let left = 0; 84 | let right = arr.length - 1; 85 | 86 | while (left < right) { 87 | const sum = arr[left] + arr[right]; 88 | 89 | if (sum === 0) { 90 | return [arr[left], arr[right]]; 91 | } else if (sum > 0) { 92 | right--; 93 | } else if (sum < 0) { 94 | left++; 95 | } 96 | } 97 | } 98 | 99 | console.log(sumZero2([-4, -3, -2, -1, 0, 1, 2, 5])); 100 | 101 | function countUniqueValues(arr) { 102 | // sorted array 103 | if (arr.length === 0) return 0; 104 | 105 | let i = 0; 106 | 107 | for (let j = 1; j < arr.length; j++) { 108 | if (arr[i] !== arr[j]) { 109 | i++; 110 | arr[i] = arr[j]; 111 | } 112 | } 113 | 114 | return i + 1; 115 | } 116 | 117 | countUniqueValues([1, 2, 2, 5, 7, 7, 99]); // 5 118 | 119 | // 3. Sliding Window Pattern 120 | 121 | // This pattern involves creating a window which can either be an array or number from one position to another 122 | // Depending on a certain condition, the window either increases or closes (and a new window is created) 123 | // Very useful for keeping track of a subset of data in an array/string etc. 124 | 125 | // My Solution 1 - naive approach, O(n^2) 126 | function maxSubArraySum(arr, n) { 127 | if (n > arr.length) { 128 | return null; 129 | } 130 | 131 | let maxSum = -Infinity; 132 | 133 | for (let j = 0; j < arr.length - n + 1; j++) { 134 | let tempSum = 0; 135 | 136 | for (let k = j; k < j + n; k++) { 137 | tempSum += arr[k]; 138 | } 139 | 140 | if (tempSum >= maxSum) { 141 | maxSum = tempSum; 142 | } 143 | } 144 | 145 | return maxSum; 146 | } 147 | 148 | console.log(maxSubArraySum([1, 2, 3, 4, 5, 8, 9], 3)); 149 | 150 | // Solution 2 - naive approach 151 | function maxSubarraySum2(arr, num) { 152 | if (num > arr.length) { 153 | return null; 154 | } 155 | var max = -Infinity; 156 | for (let i = 0; i < arr.length - num + 1; i++) { 157 | temp = 0; 158 | for (let j = 0; j < num; j++) { 159 | temp += arr[i + j]; 160 | } 161 | if (temp > max) { 162 | max = temp; 163 | } 164 | } 165 | return max; 166 | } 167 | 168 | maxSubarraySum2([2, 6, 9, 2, 1, 8, 5, 6, 3], 3); 169 | 170 | // Sliding Window Pattern, O(n) 171 | function maxSubarraySum3(arr, n) { 172 | let tempSum = 0; 173 | let maxSum = 0; 174 | 175 | for (let i = 0; i < n; i++) { 176 | maxSum += arr[i]; 177 | } 178 | 179 | tempSum = maxSum; 180 | 181 | for (let j = n; j < arr.length; j++) { 182 | tempSum = tempSum + arr[j] - arr[j - n]; 183 | maxSum = Math.max(maxSum, tempSum); 184 | } 185 | 186 | return maxSum; 187 | } 188 | 189 | console.log(maxSubarraySum3([2, 6, 9, 2, 1, 8, 5, 6, 3, 4, 4], 3)); 190 | 191 | // 53. Maximum Subarray 192 | // Given an integer array nums, find the subarray with the largest sum, and return its sum. 193 | // Kadane's Algorithm 194 | var maxSubArray = function (nums) { 195 | let maxSum = nums[0]; 196 | 197 | let currentSum = 0; 198 | 199 | for (let num of nums) { 200 | // make sure we dont't add a previous aggregated sum if it's negative and reset the currentSum to 0 201 | currentSum = Math.max(currentSum, 0); 202 | currentSum = currentSum + num; 203 | 204 | maxSum = Math.max(maxSum, currentSum); 205 | } 206 | 207 | return maxSum; 208 | }; 209 | 210 | // return the indices of the ends of the subarray 211 | function slidingWindow(nums) { 212 | let maxSum = nums[0]; 213 | let currentSum = 0; 214 | let L = 0; 215 | let maxL = 0, 216 | maxR = 0; 217 | 218 | for (let R = 0; R < nums.length; R++) { 219 | if (currentSum < 0) { 220 | currentSum = 0; 221 | L = R; 222 | } 223 | 224 | currentSum = currentSum += nums[R]; 225 | 226 | if (currentSum > maxSum) { 227 | maxSum = currentSum; 228 | maxL = L; 229 | maxR = R; 230 | } 231 | } 232 | 233 | return [maxL, maxR]; 234 | } 235 | 236 | // 219. Contains Duplicate II 237 | // Given an integer array nums and an integer k, return true if there are two 238 | // distinct indices i and j in the array such that nums[i] == nums[j] and abs(i - j) <= k. 239 | var containsNearbyDuplicate = function (nums, k) { 240 | let window = new Set(); 241 | 242 | let L = 0; 243 | 244 | for (let R = 0; R < nums.length; R++) { 245 | if (R - L > k) { 246 | window.delete(nums[L]); 247 | L++; 248 | } 249 | 250 | if (window.has(nums[R])) { 251 | return true; 252 | } 253 | 254 | window.add(nums[R]); 255 | } 256 | 257 | return false; 258 | }; 259 | 260 | // 3. Longest Substring Without Repeating Characters 261 | // Given a string s, find the length of the longest substring without duplicate characters. 262 | // Input: s = "abcabcbb" 263 | // Output: 3 264 | // Explanation: The answer is "abc", with the length of 3. 265 | 266 | // Brute force 267 | // var lengthOfLongestSubstring = function(s) { 268 | // let max = 0 269 | // for (let i = 0; i < s.length; i++) { 270 | // let seen = new Set(); 271 | // seen.add(s[i]) 272 | // let length = 1; 273 | 274 | // let j = i + 1; 275 | // while(j < s.length && !seen.has(s[j])) { 276 | // seen.add(s[j]) 277 | // length++ 278 | // j++ 279 | // } 280 | 281 | // max = Math.max(max, length) 282 | 283 | // } 284 | 285 | // return max 286 | // }; 287 | 288 | var lengthOfLongestSubstring = function (s) { 289 | let max = 0; 290 | let seen = new Set(); 291 | let left = 0, 292 | right = 0; 293 | 294 | while (right < s.length) { 295 | let c = s[right]; 296 | 297 | if (!seen.has(c)) { 298 | right++; 299 | seen.add(c); 300 | max = Math.max(max, right - left); 301 | } else { 302 | while (seen.has(c)) { 303 | seen.delete(s[left]); 304 | left++; 305 | } 306 | } 307 | } 308 | 309 | return max; 310 | }; 311 | 312 | // 209. Minimum Size Subarray Sum 313 | // Given an array of positive integers nums and a positive integer target, return the 314 | // minimal length of a subarray whose sum is greater than or equal to target. 315 | // If there is no such subarray, return 0 instead: O(n) 316 | function shortestSubarray(nums, target) { 317 | let L = 0, 318 | total = 0; 319 | let length = Infinity; 320 | 321 | for (let R = 0; R < nums.length; R++) { 322 | total += nums[R]; 323 | while (total >= target) { 324 | length = Math.min(R - L + 1, length); 325 | total -= nums[L]; 326 | L++; 327 | } 328 | } 329 | 330 | if (length == Infinity) { 331 | return 0; 332 | } 333 | return length; 334 | } 335 | 336 | // 4. Divide and Conquer pattern 337 | 338 | // This pattern involves dividing a data set into smaller chunks and then repeating a process with a subset of data. 339 | // This pattern can tremendously decrease time complexity 340 | 341 | // 5. Prefix Sum 342 | 343 | class PrefixSum { 344 | constructor(nums) { 345 | this.prefix = new Array(); 346 | let total = 0; 347 | for (let n of nums) { 348 | total += n; 349 | this.prefix.push(total); 350 | } 351 | } 352 | 353 | rangeSum(left, right) { 354 | let preRight = this.prefix[right]; 355 | let preLeft = left > 0 ? this.prefix[left - 1] : 0; 356 | return preRight - preLeft; 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /02.recursion.js: -------------------------------------------------------------------------------- 1 | // what's the smallest amount or unit of work I can do to contribute to reach 2 | // the end goal (base case) 3 | 4 | 5 | 6 | // Recursive Version 7 | function countDown(num){ 8 | // base case 9 | if(num <= 0) { 10 | console.log("All done!"); 11 | return; 12 | } 13 | console.log(num); 14 | num--; 15 | countDown(num); 16 | } 17 | countDown(3) 18 | 19 | // Iterative Version 20 | function countDown(num){ 21 | for(var i = num; i > 0; i--){ 22 | console.log(i); 23 | } 24 | console.log("All done!") 25 | } 26 | 27 | 28 | function sumRange(num){ 29 | if(num === 1) return 1; 30 | return num + sumRange(num-1); 31 | } 32 | 33 | sumRange(4) 34 | // return 4 + sumRange(3) 35 | // return 3 + sumRange(2) 36 | // return 2 + sumRange(1) 37 | // return 1 38 | // 39 | // 4 + 3 + 2 + 1 40 | // 41 | // 10 42 | 43 | 44 | function factorialIterative(num){ 45 | let total = 1; 46 | for(let i = num; i > 1; i--){ 47 | total *= i 48 | } 49 | return total; 50 | } 51 | 52 | 53 | function factorialRecursive(num) { 54 | if (num === 1) { 55 | return 1 56 | } 57 | 58 | return num * factorialRecursive(num -1) 59 | } 60 | 61 | 62 | console.log(factorialRecursive(4)); 63 | 64 | 65 | // Tail Recusrion 66 | // O(1) space complexity 67 | function tailFactorial(num, resultSoFar = 1) { 68 | if(num === 0) { 69 | return resultSoFar 70 | } 71 | 72 | return tailFactorial(num - 1, resultSoFar * num); 73 | } 74 | 75 | 76 | // string reversal 77 | function reverseStr(str) { 78 | if(str === "") { 79 | return "" 80 | } 81 | 82 | return reverseStr(str.slice(1)) + str[0] 83 | } 84 | 85 | reverseStr("hello") 86 | 87 | 88 | // check if str is a palindrome 89 | function isPalindrome(str) { 90 | 91 | // base case / stopping condition 92 | if(str.length === 0 || str.length === 1) { 93 | return true 94 | } 95 | 96 | // Do some work to shrink the problem space to reach the base case 97 | if(str[0] === str[str.length -1]) { 98 | return isPalindrome(str.slice(1, str.length -1)) 99 | } 100 | 101 | // Additional base case to handle non-palindromes 102 | return false 103 | } 104 | 105 | isPalindrome("racecar") 106 | 107 | 108 | // Helper Method Recursion 109 | 110 | function outer(input){ 111 | 112 | var outerScopedVariable = [] 113 | 114 | function helper(helperInput){ 115 | // modify the outerScopedVariable 116 | helper(helperInput--) 117 | } 118 | 119 | helper(input) 120 | 121 | return outerScopedVariable; 122 | 123 | } 124 | 125 | function collectOddValues(arr){ 126 | 127 | let result = [] 128 | 129 | function helper(helperInput){ 130 | if(helperInput.length === 0) { 131 | return; 132 | } 133 | 134 | if(helperInput[0] % 2 !== 0){ 135 | result.push(helperInput[0]) 136 | } 137 | 138 | helper(helperInput.slice(1)) 139 | } 140 | 141 | helper(arr) 142 | 143 | return result; 144 | 145 | } 146 | 147 | // Pure Recursion 148 | 149 | function collectOddValues2(arr){ 150 | // if we define a variable inside of a recursive function it's going to be reset or reassigned 151 | // to an empty [] everytime the function is called recursively 152 | let newArr = []; 153 | 154 | if(arr.length === 0) { 155 | return newArr; 156 | } 157 | 158 | if(arr[0] % 2 !== 0){ 159 | newArr.push(arr[0]); 160 | } 161 | 162 | newArr = newArr.concat(collectOddValues2(arr.slice(1))); 163 | return newArr; 164 | } 165 | 166 | collectOddValues2([1,2,3,4,5]) 167 | // return [1].concat(collectOddValues2([2,3,4,5])) 168 | // return [].concat(collectOddValues2([3,4,5])) 169 | // return [3].concat(collectOddValues2([4,5])) 170 | // return [].concat(collectOddValues2([5])) 171 | // return [5].concat(collectOddValues2([])) 172 | // return [] 173 | // 174 | // [1]. concat([]. concat([3]. concat([]. concat([5]. concat([]))))) 175 | // 176 | // [1, 3, 5] 177 | 178 | 179 | // Pure Recursion Tips 180 | // 1. For arrays, use methods like slice, the spread operator, and concat that make copies of arrays so you do not mutate them 181 | // 2. Remember that strings are immutable so you will need to use methods like slice, substr, or substring to make copies of strings 182 | // 3. To make copies of objects use Object.assign, or the spread operator 183 | -------------------------------------------------------------------------------- /03.searching.js: -------------------------------------------------------------------------------- 1 | // Linear Search - O(n) 2 | function linearSearch(arr, val){ 3 | for(var i = 0; i < arr.length; i++){ 4 | if(arr[i] === val) return i; 5 | } 6 | return -1; 7 | } 8 | 9 | linearSearch([34,51,1,2,3,45,56,687], 100) 10 | 11 | 12 | // Binary Search - O( log(n) ) 13 | // Binary search is a much faster form of search 14 | // Rather than eliminating one element at a time, you can eliminate half of the remaining elements at a time (Divide and Conquer) 15 | // Binary search only works on sorted arrays! 16 | 17 | function binarySearch(arr, ele) { 18 | let start = 0; 19 | let end = arr.length -1 20 | let middle = Math.floor((start + end) / 2) 21 | 22 | 23 | while(arr[middle] !== ele && start <= end) { 24 | 25 | if(ele > arr[middle]) { 26 | start = middle + 1 27 | } else if (ele < arr[middle]) { 28 | end = middle - 1 29 | } 30 | 31 | middle = Math.floor((start + end) / 2) 32 | } 33 | 34 | if( arr[middle] === ele) { 35 | return middle 36 | } else { 37 | return -1 38 | } 39 | } 40 | 41 | 42 | // const binarySearch = (nums, target) => { 43 | // let left = 0; 44 | // let right = nums.length - 1; 45 | // while (left <= right) { 46 | // const mid = Math.floor((left + right) / 2); 47 | // const foundVal = nums[mid]; 48 | // if (foundVal === target) { 49 | // return mid; 50 | // } else if (foundVal < target) { 51 | // left = mid + 1; 52 | // } else { 53 | // right = mid - 1; 54 | // } 55 | // } 56 | // 57 | // return null; 58 | // }; 59 | 60 | console.log(binarySearch([1, 2, 3, 4, 5, 6, 7, 8, 9, 100], 10)) 61 | 62 | 63 | // Recursive 64 | function binarySearchRecursive(arr, ele, left=0, right=arr.length-1) { 65 | 66 | if(left > right) { 67 | return -1 68 | } 69 | 70 | const midPoint = Math.floor((left + right) / 2) 71 | 72 | if(arr[midPoint] === ele) { 73 | return midPoint 74 | } 75 | 76 | if(ele > arr[midPoint]) { 77 | return binarySearch(arr, ele, midPoint + 1, right) 78 | } 79 | 80 | if(num < arr[midPoint]) { 81 | return binarySearch(arr, ele, left, midPoint -1) 82 | } 83 | } 84 | 85 | 86 | // naive algorithm or approach, )(n*m) 87 | function subStrSearch(long, short){ 88 | // saalik 89 | // ali 90 | let matches = {} 91 | for(let i = 0; i < long.length; i++) { 92 | 93 | for(let j = 0; j < short.length; j++) { 94 | 95 | if (short[j] !== long[i+j]) { 96 | break 97 | } 98 | 99 | if(j === short.length - 1) { 100 | matches[short] = (matches[short] || 0) + 1 101 | } 102 | 103 | } 104 | 105 | } 106 | return matches 107 | } 108 | 109 | 110 | console.log(subStrSearch("there are many students there", "re")) 111 | 112 | 113 | // Building the Table 114 | function matchTable(word) { 115 | let arr = Array.from({ length: word.length }).fill(0); 116 | let suffixEnd = 1; 117 | let prefixEnd = 0; 118 | while (suffixEnd < word.length) { 119 | if (word[suffixEnd] === word[prefixEnd]) { 120 | // we can build a longer prefix based on this suffix 121 | // store the length of this longest prefix 122 | // move prefixEnd and suffixEnd 123 | prefixEnd += 1; 124 | arr[suffixEnd] = prefixEnd; 125 | suffixEnd += 1; 126 | } else if (word[suffixEnd] !== word[prefixEnd] && prefixEnd !== 0) { 127 | // there's a mismatch, so we can't build a larger prefix 128 | // move the prefixEnd to the position of the next largest prefix 129 | prefixEnd = arr[prefixEnd - 1]; 130 | } else { 131 | // we can't build a proper prefix with any of the proper suffixes 132 | // let's move on 133 | arr[suffixEnd] = 0; 134 | suffixEnd += 1; 135 | } 136 | } 137 | return arr; 138 | } 139 | 140 | // KMP Algorithm - O(n + m) time, O(m) space 141 | // KMP provides a linear time algorithm for searches in strings 142 | function kmpSearch(long, short) { 143 | let table = matchTable(short); 144 | let shortIdx = 0; 145 | let longIdx = 0; 146 | let count = 0; 147 | while (longIdx < long.length - short.length + shortIdx + 1) { 148 | if (short[shortIdx] !== long[longIdx]) { 149 | // we found a mismatch :( 150 | // if we just started searching the short, move the long pointer 151 | // otherwise, move the short pointer to the end of the next potential prefix 152 | // that will lead to a match 153 | if (shortIdx === 0) longIdx += 1; 154 | else shortIdx = table[shortIdx - 1]; 155 | } else { 156 | // we found a match! shift both pointers 157 | shortIdx += 1; 158 | longIdx += 1; 159 | // then check to see if we've found the substring in the large string 160 | if (shortIdx === short.length) { 161 | // we found a substring! increment the count 162 | // then move the short pointer to the end of the next potential prefix 163 | count++; 164 | shortIdx = table[shortIdx - 1]; 165 | } 166 | } 167 | } 168 | return count; 169 | } 170 | -------------------------------------------------------------------------------- /04.bubbleSort.js: -------------------------------------------------------------------------------- 1 | // Largest numbers bubbles to the end of the list until we have a sorted list. 2 | 3 | // function bubbleSort(arr) { 4 | // 5 | // for(let i = 0; i < arr.length; i++) { 6 | // 7 | // for(let j = 0; j < arr.length - i; j++) { 8 | // 9 | // if(arr[j] > arr[j + 1]) { 10 | // let larger = arr[j]; 11 | // arr[j] = arr[j + 1] 12 | // arr[j + 1] = larger 13 | // } 14 | // } 15 | // 16 | // } 17 | // 18 | // return arr; 19 | // } 20 | 21 | 22 | // runtime complexity of n^2 (worst case), n (best case) 23 | // O(1) space complexity 24 | function bubbleSort(arr) { 25 | 26 | for(let i = arr.length; i > 0; i--) { 27 | 28 | for(let j = 0; j < i - 1; j++) { 29 | 30 | if(arr[j] > arr[j + 1]) { 31 | let larger = arr[j]; 32 | arr[j] = arr[j + 1] 33 | arr[j + 1] = larger 34 | } 35 | } 36 | 37 | } 38 | 39 | return arr; 40 | } 41 | 42 | 43 | // OPTIMIZED 44 | function bubbleSortOptimised(arr) { 45 | let noSwaps; 46 | for(let i = arr.length; i > 0; i--) { 47 | noSwaps = true; 48 | for(let j = 0; j < i - 1; j++) { 49 | if(arr[j] > arr[j + 1]) { 50 | 51 | let larger = arr[j]; 52 | arr[j] = arr[j + 1] 53 | arr[j + 1] = larger; 54 | 55 | noSwaps = false; 56 | } 57 | } 58 | 59 | if(noSwaps) break; 60 | } 61 | 62 | return arr; 63 | } 64 | 65 | 66 | console.log(bubbleSortOptimised([18, 10, 14, 15, 16, 17])) 67 | 68 | 69 | // OR 70 | function bubbleSort2(arr) { 71 | 72 | let isSorted = false; 73 | let counter = 0; 74 | 75 | while(!isSorted) { 76 | isSorted = true; 77 | for (let i = 0; i < arr.length - counter; i++) { 78 | if(arr[i] > arr[i+1]) { 79 | 80 | let larger = arr[j]; 81 | arr[j] = arr[j + 1] 82 | arr[j + 1] = larger; 83 | 84 | isSorted = false; 85 | } 86 | } 87 | 88 | counter += 1; 89 | } 90 | 91 | return arr; 92 | } 93 | -------------------------------------------------------------------------------- /05.selectionSort.js: -------------------------------------------------------------------------------- 1 | // smallest numbers moves or sinks to the beginning of the list until list is sorted. 2 | 3 | // runtime complexity of n^2 (worst case), n^2 (best case) 4 | // O(1) space complexity 5 | function selectionSort(arr) { 6 | 7 | for(let i = 0; i < arr.length; i++) { 8 | 9 | let indexOfMin = i; 10 | 11 | for(let j = i + 1; j < arr.length; j++) { 12 | 13 | if(arr[j] < arr[indexOfMin]) { 14 | indexOfMin = j; 15 | } 16 | 17 | } 18 | 19 | if(indexOfMin !== i) { 20 | let min = arr[indexOfMin]; 21 | arr[indexOfMin] = arr[i] 22 | arr[i] = min 23 | } 24 | 25 | } 26 | 27 | return arr; 28 | } 29 | 30 | 31 | console.log(selectionSort([18, 10, 14, 0, 1, 3, 2, 15, 16, 17, -3])) 32 | -------------------------------------------------------------------------------- /06.insertionSort.js: -------------------------------------------------------------------------------- 1 | 2 | // runtime complexity of n^2 (worst case), n (best case) 3 | // O(1) space complexity 4 | // 1, 2, 4, 5, 6, 3, 5 | function insertionSort(arr) { 6 | 7 | // unsorted part 8 | for(let i = 1; i < arr.length; i++) { 9 | let current = arr[i]; 10 | 11 | let j = i - 1; 12 | 13 | // sorted part 14 | while(j >= 0 && arr[j] > current) { 15 | arr[j+1] = arr[j] 16 | j-- 17 | } 18 | 19 | arr[j + 1] = current 20 | } 21 | 22 | return arr; 23 | } 24 | 25 | console.log(insertionSort([18, 10, 14, 0, 1, 3, 2, 15, 16, 17, -3])) 26 | 27 | 28 | // OR 29 | function insertionSort2(arr) { 30 | 31 | for(let i = 1; i < arr.length; i++) { 32 | let j = i; 33 | 34 | while(j > 0 && arr[j] < arr[j - 1]) { 35 | let smaller = arr[j]; 36 | arr[j] = arr[j - 1] 37 | arr[j - 1] = smaller; 38 | 39 | j-- 40 | } 41 | } 42 | 43 | return arr; 44 | } 45 | 46 | console.log(insertionSort2([18, 10, 14, 0, 1, 3, 2, 15, 16, 17, -3])) 47 | -------------------------------------------------------------------------------- /07.mergeSort.js: -------------------------------------------------------------------------------- 1 | // 2 | // Merge Sort 3 | // It's a combination of two things - merging and sorting! 4 | // Exploits the fact that arrays of 0 or 1 element are always sorted 5 | // Works by decomposing an array into smaller arrays of 0 or 1 elements, then building up a newly sorted array 6 | // O(n log n) -> Best, average and worst time complexity | O(logn) decompositions and O(n) comparisons per decomposition 7 | // O(n) -> space complexity 8 | 9 | 10 | 11 | // [1, 2], [5, 8, 9, 10] 12 | // function mergeSortedArrays(arr1, arr2) { 13 | // let mergedArr = [] 14 | // 15 | // while(arr1.length > 0 && arr2.length > 0) { 16 | // if(arr1[0] > arr2[0]) { 17 | // mergedArr.push(arr2[0]) 18 | // arr2.splice(0, 1) 19 | // } else if (arr1[0] < arr2[0]) { 20 | // mergedArr.push(arr1[0]) 21 | // arr1.splice(0, 1) 22 | // } 23 | // 24 | // console.log(arr1, arr2) 25 | // } 26 | // 27 | // while (arr1.length > 0) { 28 | // mergedArr.push(arr1[0]) 29 | // arr1.splice(0, 1) 30 | // } 31 | // 32 | // while (arr2.length > 0) { 33 | // mergedArr.push(arr2[0]) 34 | // arr2.splice(0, 1) 35 | // } 36 | // 37 | // return mergedArr 38 | // } 39 | 40 | 41 | // This function runs in O(n + m) time and O(n + m) space and should not modify the parameters passed to it. 42 | function mergeSortedArrays(arr1, arr2){ 43 | let results = []; 44 | let i = 0; 45 | let j = 0; 46 | while(i < arr1.length && j < arr2.length){ 47 | if(arr2[j] > arr1[i]){ 48 | results.push(arr1[i]); 49 | i++; 50 | } else { // arr1[i] >= arr2[j] 51 | results.push(arr2[j]) 52 | j++; 53 | } 54 | } 55 | 56 | // push remaining elements 57 | while(i < arr1.length) { 58 | results.push(arr1[i]) 59 | i++; 60 | } 61 | while(j < arr2.length) { 62 | results.push(arr2[j]) 63 | j++; 64 | } 65 | return results; 66 | } 67 | 68 | // console.log(mergeSortedArrays([100,200], [1,2,3,5,6])) 69 | 70 | 71 | function mergeSort(arr) { 72 | 73 | if(arr.length === 1) { 74 | return arr 75 | } 76 | 77 | const center = Math.floor(arr.length / 2); 78 | const left = arr.slice(0, center); 79 | const right = arr.slice(center); 80 | 81 | return merge(mergeSort(left), mergeSort(right)); 82 | } 83 | 84 | console.log(mergeSort([18, 10, 14, 15, 16, 17])) 85 | -------------------------------------------------------------------------------- /08.quickSort.js: -------------------------------------------------------------------------------- 1 | // Big O of Quicksort 2 | 3 | // Time Complexity(Best) - O(n log n) 4 | // Time Complexity(Average) - O(n log n) 5 | // Time Complexity(Worst) - O(n^2) 6 | // Space Complexity - O(log n) 7 | 8 | // O(n log n) = O(log n) decompositions + O(n) comparisons per decomposition 9 | // O(n^2) = O(n) decompositions + O(n) comparisons per decomposition (worst case, if array is already sorted) 10 | // if we are picking the minimum or maximum everytime as pivot repeatedly, that results in quadratic time 11 | 12 | function pivotHelper(arr, start = 0, end = arr.length-1) { 13 | // We are assuming the pivot is always the first element 14 | let pivot = arr[start] // element 15 | let swapIndex = start 16 | 17 | for(let i= start + 1; i <= end; i++) { 18 | if(arr[i] < pivot) { 19 | 20 | swapIndex++ 21 | [arr[swapIndex], arr[i]] = [arr[i], arr[swapIndex]] 22 | 23 | // OR 24 | // [arr[swapIndex + 1], arr[i]] = [arr[i], arr[swapIndex + 1]] 25 | // swapIndex++ 26 | } 27 | } 28 | 29 | [arr[swapIndex], arr[start]] = [arr[start], arr[swapIndex]] 30 | return swapIndex; 31 | } 32 | 33 | 34 | function quickSort(arr, left = 0, right = arr.length - 1){ 35 | 36 | if(left < right) { 37 | let pivotIndex = pivotHelper(arr, left, right); // 4 38 | 39 | // left 40 | quickSort(arr, left, pivotIndex - 1) 41 | 42 | // right 43 | quickSort(arr, pivotIndex + 1, right); 44 | } 45 | 46 | return arr; 47 | } 48 | 49 | // pivotHelper([ 5, 3, 9, 7, 1, 2, 4]) 50 | console.log(quickSort([ 5, 3, 9, 7, 1, 2, 4])) // [1, 2, 3, 4, 5, 7, 9] 51 | 52 | 53 | // Hoare's QuickSelect Algorithm, to find the kth smallest element in the unordered list; 54 | // O(n) best time complexity, O(n^2) worst time complexity 55 | // O(1) space complexity; tail recursion 56 | function quickSelect(arr, k, left = 0, right = arr.length - 1) { 57 | 58 | let pivotIndex = pivotHelper(arr, left, right); 59 | let indexToFind = k - 1; 60 | 61 | if(pivotIndex === indexToFind) { 62 | return arr[pivotIndex] 63 | } else if (pivotIndex < indexToFind) { 64 | return quickSelect(arr, k, pivotIndex + 1, right) 65 | } else if (pivotIndex > indexToFind) { 66 | return quickSelect(arr, indexToFind, left, pivotIndex - 1) 67 | } 68 | } 69 | 70 | console.log(quickSelect([ 5, 3, 9, 7, 1, 2, 4], 7)) // 9 71 | -------------------------------------------------------------------------------- /09.radixSort.js: -------------------------------------------------------------------------------- 1 | // COMPARISON SORTS 2 | 3 | // Average Time Complexity: 4 | // Bubble Sort - O(n^2) 5 | // Insertion Sort - O(n^2) 6 | // Selection Sort - O(n^2) 7 | // Quick Sort - O(n log (n)) 8 | // Merge Sort - O(n log (n)) 9 | 10 | // Can we do better? YES, BUT NOT BY MAKING COMPARISONS 11 | 12 | // RADIX SORT: 13 | // Radix sort is a special sorting algorithm that works on lists of numbers 14 | // It never makes comparisons between elements! 15 | // It exploits the fact that information about the size of a number is encoded in the number of digits. 16 | // More digits means a bigger number! 17 | 18 | // RADIX SORT BIG O: 19 | // Time Complexity (Best) - O(nk) 20 | // Time Complexity (Average) - O(nk) 21 | // Time Complexity (Worst) - O(nk) 22 | // Space Complexity - O(n + k) 23 | // n - length of array 24 | // k is the max number of digits, word size 25 | 26 | 27 | // returns the digit in num at the given place value 28 | function getDigit(num, i) { 29 | return Math.floor(Math.abs(num) / Math.pow(10, i)) % 10; 30 | } 31 | 32 | // returns the number of digits in num 33 | function digitCount(num) { 34 | if (num === 0) return 1; 35 | return Math.floor(Math.log10(Math.abs(num))) + 1; 36 | } 37 | 38 | // Given an array of numbers, returns the number of digits in the largest number in the list 39 | function mostDigits(nums) { 40 | let maxDigits = 0; 41 | for (let i = 0; i < nums.length; i++) { 42 | maxDigits = Math.max(maxDigits, digitCount(nums[i])); 43 | } 44 | return maxDigits; 45 | } 46 | 47 | 48 | function radixSort(nums){ 49 | let maxDigitCount = mostDigits(nums); 50 | for(let k = 0; k < maxDigitCount; k++){ 51 | 52 | let digitBuckets = Array.from({length: 10}, () => []); 53 | 54 | for(let i = 0; i < nums.length; i++){ 55 | let digit = getDigit(nums[i], k); 56 | digitBuckets[digit].push(nums[i]); 57 | } 58 | 59 | nums = [].concat(...digitBuckets); 60 | } 61 | 62 | return nums; 63 | } 64 | 65 | console.log(radixSort([23,345,5467,12,2345,9852])) // [12, 23, 345, 2345, 5467, 9852] 66 | -------------------------------------------------------------------------------- /10.Arrays.js: -------------------------------------------------------------------------------- 1 | class MyArray { 2 | constructor() { 3 | this.length = 0; 4 | this.data = {}; 5 | } 6 | 7 | get(index) { 8 | return this.data[index]; 9 | } 10 | 11 | push(item) { 12 | this.data[this.length] = item; 13 | this.length++; 14 | return this.data; 15 | } 16 | 17 | pop() { 18 | const lastItem = this.data[this.length - 1]; 19 | delete this.data[this.length - 1]; 20 | this.length--; 21 | return lastItem; 22 | } 23 | 24 | deleteAtIndex(index) { 25 | const item = this.data[index]; 26 | this.shiftItems(index); 27 | return item; 28 | } 29 | 30 | shiftItems(index) { 31 | for (let i = index; i < this.length - 1; i++) { 32 | this.data[i] = this.data[i + 1]; 33 | } 34 | console.log(this.data[this.length - 1]); 35 | delete this.data[this.length - 1]; 36 | this.length--; 37 | } 38 | 39 | } 40 | 41 | const myArray = new MyArray(); 42 | myArray.push('hi'); 43 | myArray.push('you'); 44 | myArray.push('!'); 45 | myArray.pop(); 46 | myArray.deleteAtIndex(0); 47 | myArray.push('are'); 48 | myArray.push('nice'); 49 | myArray.shiftItems(0); 50 | console.log(myArray); 51 | -------------------------------------------------------------------------------- /11.SinglyLinkedLists.js: -------------------------------------------------------------------------------- 1 | // What is a linked list? 2 | // A data structure that contains a head, tail and length property. 3 | // Linked Lists consist of nodes, and each node has a value and a pointer to another node or null 4 | 5 | 6 | // Big O of Singly Linked Lists 7 | // Insertion - O(1) 8 | // Removal - It depends.... O(1) or O(N) 9 | // Searching - O(N) 10 | // Access - O(N) 11 | 12 | class Node { 13 | constructor(value) { 14 | this.value = value 15 | this.next = null 16 | } 17 | } 18 | 19 | 20 | class SinglyLinkedList { 21 | constructor() { 22 | this.head = null 23 | this.tail = null 24 | this.length = 0 25 | } 26 | 27 | push(value) { 28 | const newNode = new Node(value) 29 | 30 | // if list is empty 31 | if(!this.head) { 32 | this.head = this.tail = newNode 33 | this.length++ 34 | return this 35 | } 36 | 37 | // there are some existing nodes in the list 38 | const currentTail = this.tail; 39 | currentTail.next = newNode 40 | this.tail = newNode 41 | this.length++ 42 | return this 43 | } 44 | 45 | 46 | // pop() { 47 | // 48 | // // if there is no element in the list 49 | // if(!this.head) { 50 | // return null 51 | // } 52 | // 53 | // // if there is only one element in the list 54 | // if(!this.head.next) { 55 | // const currentTail = this.tail; 56 | // this.head = this.tail = null; 57 | // this.length-- 58 | // return currentTail 59 | // } 60 | // 61 | // let secondLast = this.head; 62 | // 63 | // while(secondLast.next.next) { 64 | // secondLast = secondLast.next 65 | // } 66 | // 67 | // secondLast.next = null; 68 | // let currentTail = this.tail // last 69 | // this.tail = secondLast 70 | // this.length-- 71 | // 72 | // return currentTail; 73 | // } 74 | 75 | // OR 76 | 77 | pop() { 78 | 79 | // if there is no element in the list 80 | if(!this.head) { 81 | return null 82 | } 83 | 84 | let secondLast = this.head 85 | let current = this.head; //removeLast 86 | 87 | while(current.next) { 88 | secondLast = current; 89 | current = current.next 90 | } 91 | 92 | secondLast.next = null 93 | this.tail = secondLast 94 | this.length-- 95 | 96 | // catches the case when there is only one node in the list 97 | if(this.length === 0) { 98 | this.head = null 99 | this.tail = null 100 | } 101 | 102 | return current 103 | } 104 | 105 | shift() { 106 | // if there is no element in the list 107 | if(!this.head) { 108 | return null 109 | } 110 | // if there is only one element in the list 111 | if(!this.head.next) { 112 | const currentHead = this.head 113 | this.head = this.tail = null 114 | this.length-- 115 | return currentHead 116 | } 117 | 118 | const currentHead = this.head; 119 | const newHead = currentHead.next 120 | this.head = newHead 121 | this.length-- 122 | return currentHead 123 | } 124 | 125 | unshift(value) { 126 | const newNode = new Node(value); 127 | const currentHead = this.head; 128 | newNode.next = currentHead 129 | this.head = newNode 130 | this.length++ 131 | 132 | // if list was empty 133 | if(!currentHead) { 134 | this.tail = newNode 135 | } 136 | 137 | return this 138 | } 139 | 140 | get(index) { 141 | 142 | if(index < 0 || index > this.length -1) { 143 | return null 144 | } 145 | 146 | let current = this.head; 147 | let i = 0 148 | while(i < index) { // OR while(i !== index) 149 | current = current.next; 150 | i++ 151 | } 152 | 153 | return current 154 | } 155 | 156 | 157 | set(index, value) { 158 | const foundNode = this.get(index); 159 | 160 | if(foundNode) { 161 | foundNode.value = value; 162 | return true 163 | } 164 | 165 | return false 166 | } 167 | 168 | insert(index, value) { 169 | if(index < 0 || index > this.length) { 170 | return false 171 | } 172 | 173 | if(index === this.length) { 174 | return !!this.push(value) 175 | } 176 | 177 | if(index === 0) { 178 | return !!this.unshift(value) 179 | } 180 | 181 | const newNode = new Node(value); 182 | let previous = this.get(index - 1) 183 | let current = previous.next 184 | previous.next = newNode; 185 | newNode.next = current; 186 | this.length++ 187 | return true 188 | 189 | } 190 | 191 | remove(index) { 192 | if(index < 0 || index >= this.length) { 193 | return null 194 | } 195 | 196 | if(index === this.length - 1) { 197 | return this.pop() 198 | } 199 | 200 | if(index === 0) { 201 | return this.shift() 202 | } 203 | 204 | let previous = this.get(index - 1) 205 | let toBeRemoved = previous.next; 206 | let next = toBeRemoved.next; 207 | 208 | previous.next = next 209 | this.length-- 210 | return toBeRemoved; 211 | } 212 | 213 | 214 | // Reversing the Linked List in place - O(n) time 215 | // 1 -> 2 -> 3 -> 4 -> 5 -> 6 216 | reverse() { 217 | let currentHead = this.head; 218 | let currentTail = this.tail; 219 | 220 | // flip head and tail 221 | this.head = currentTail 222 | this.tail = currentHead; 223 | 224 | let prev = null 225 | while(currentHead) { // for(var i = 0; i < this.length; i++) 226 | let next = currentHead.next; 227 | currentHead.next = prev; 228 | prev = currentHead 229 | currentHead = next 230 | } 231 | 232 | // prev will now contain the new head of the linked list 233 | return prev; 234 | 235 | } 236 | 237 | // 1 -> 2 -> 3 -> 4 -> 5 -> 6 238 | // reverseRecursive(head, prev=null) { 239 | // if(head === null) { 240 | // return prev 241 | // } 242 | // 243 | // const next = head.next; 244 | // head.next = prev; 245 | // // prev = head; 246 | // return reverseRecursive(next, head) 247 | // } 248 | 249 | print() { 250 | const values = []; 251 | let current = this.head; 252 | 253 | while(current) { 254 | values.push(current.value); 255 | current = current.next; 256 | } 257 | 258 | console.log(values); 259 | } 260 | 261 | } 262 | 263 | 264 | const l = new SinglyLinkedList(); 265 | 266 | l.push(1) 267 | l.push(2) 268 | l.push(3) 269 | l.push(4) 270 | l.push(5) 271 | l.push(6) 272 | // a = l.unshift("hello") 273 | // l.shift() 274 | // let b = l.pop() 275 | // console.log(a) 276 | // console.log(l.head, l.length) 277 | // console.log(l) 278 | // console.log(l.insert(6, "hello")) 279 | // console.log(l.get(2)) 280 | // console.log(l.remove(1)) 281 | // console.log(l) 282 | // l.print() 283 | // l.reverse() 284 | // l.print() 285 | 286 | 287 | const l2 = new SinglyLinkedList(); 288 | l2.push("a") 289 | l2.push("b") 290 | l2.push("c") 291 | l2.push("d"); 292 | 293 | function zipperLists(head1, head2) { 294 | let head = head1; 295 | let current1 = head1.next; 296 | let current2 = head2; 297 | let count = 0; 298 | 299 | while(current1 && current2) { 300 | if(count % 2 === 0) { 301 | head1.next = current2 302 | current2 = current2.next 303 | } else { 304 | head1.next = current1; 305 | current1 = current1.next 306 | } 307 | 308 | count++ 309 | head1 = head1.next; 310 | } 311 | 312 | if(current1) { 313 | head1.next = current1 314 | } 315 | 316 | if(current2) { 317 | head1.next = current2 318 | } 319 | 320 | return head; 321 | } 322 | 323 | 324 | let zippedList = zipperLists(l.head, l2.head); // [1, 'a', 2, 'b', 3, 'c', 4, 'd', 5, 6] 325 | console.log(zippedList) 326 | 327 | 328 | // Floyd's Tortoise And Hare Algorithm 329 | // Cycle Detection Algorithm, Given the head of a linked list, return the node where the cycle begins. 330 | // If there is no cycle, return null. 331 | // O(n) time, O(1) time; 332 | var detectCycle = function(head) { 333 | 334 | let tortoise = head; 335 | let hare = head; 336 | 337 | while(tortoise?.next && hare?.next?.next) { 338 | tortoise = tortoise.next; 339 | hare = hare.next.next; 340 | 341 | if(tortoise === hare) { 342 | 343 | hare = head; 344 | while(hare !== tortoise) { 345 | hare = hare.next; 346 | tortoise = tortoise.next 347 | } 348 | 349 | return hare 350 | } 351 | } 352 | 353 | return null 354 | 355 | }; 356 | 357 | 358 | // Naive Implementation -> O(n) time, O(n) space 359 | // let detectCycle = function(head) { 360 | 361 | 362 | // let current = head; 363 | // let visited = new Set(); 364 | 365 | // while(current) { 366 | 367 | // if(visited.has(current)) { 368 | // return current 369 | // } else { 370 | // visited.add(current) 371 | // } 372 | 373 | // current = current.next 374 | // } 375 | 376 | // return null 377 | 378 | // }; 379 | -------------------------------------------------------------------------------- /12.DoublyLinkedLists.js: -------------------------------------------------------------------------------- 1 | // Big O of Doubly Linked Lists 2 | // Insertion - O(1) 3 | // Removal - O(1) 4 | // Searching - O(N) 5 | // Access - O(N) 6 | // Technically searching is O(N / 2), but that's still O(N) 7 | 8 | // Doubly Linked Lists are almost identical to Singly Linked Lists except there is an additional pointer to previous nodes 9 | // Better than Singly Linked Lists for finding nodes and can be done in half the time! 10 | // However, they do take up more memory considering the extra pointer 11 | // Doubly linked lists are used to implement other data structures and certain types of caches 12 | 13 | class Node { 14 | constructor(value) { 15 | this.value = value 16 | this.next = null 17 | this.prev = null 18 | } 19 | } 20 | 21 | 22 | class DoublyLinkedList { 23 | constructor() { 24 | this.head = null 25 | this.tail = null 26 | this.length = 0 27 | } 28 | 29 | 30 | push(value) { 31 | const newNode = new Node(value); 32 | 33 | // if list is empty 34 | if(!this.head) { 35 | this.head = this.tail = newNode 36 | this.length++ 37 | return this 38 | } 39 | 40 | const currentTail = this.tail; 41 | currentTail.next = newNode; 42 | newNode.prev = currentTail 43 | this.tail = newNode 44 | this.length++ 45 | return this; 46 | } 47 | 48 | 49 | pop() { 50 | 51 | // if there is no element in the list 52 | if(!this.head) { 53 | return null 54 | } 55 | 56 | // if there is only one element in the list 57 | if(!this.head.next) { 58 | const currentTail = this.tail; 59 | this.head = this.tail = null; 60 | this.length-- 61 | return currentTail 62 | } 63 | 64 | const last = this.tail; 65 | const secondLast = last.prev; 66 | secondLast.next = null; 67 | last.prev = null 68 | this.tail = secondLast; 69 | this.length-- 70 | return last; 71 | } 72 | 73 | 74 | shift() { 75 | // if there is no element in the list 76 | if(!this.head) { 77 | return null 78 | } 79 | // if there is only one element in the list 80 | if(!this.head.next) { 81 | const currentHead = this.head 82 | this.head = this.tail = null 83 | this.length-- 84 | return currentHead 85 | } 86 | 87 | const currentHead = this.head; 88 | const newHead = currentHead.next; 89 | currentHead.next = null; 90 | newHead.prev = null; 91 | this.head = newHead; 92 | this.length-- 93 | return currentHead 94 | } 95 | 96 | unshift(value) { 97 | const newNode = new Node(value); 98 | const currentHead = this.head; 99 | 100 | // if list was empty 101 | if(!currentHead) { 102 | this.head = this.tail = newNode; 103 | this.length++ 104 | return this; 105 | } 106 | 107 | newNode.next = currentHead 108 | currentHead.prev = newNode 109 | this.head = newNode 110 | this.length++ 111 | return this; 112 | } 113 | 114 | 115 | get(index) { 116 | 117 | if(index < 0 || index >= this.length) { 118 | return null 119 | } 120 | 121 | const fromStart = index <= Math.floor(this.length / 2) ? true : false 122 | let current, count; 123 | 124 | if(fromStart) { 125 | console.log("Working from the start...!") 126 | current = this.head; 127 | count = 0 128 | while(count < index) { // OR while(count !== index) 129 | current = current.next; 130 | count++ 131 | } 132 | 133 | } else { 134 | console.log("Working from the end...!") 135 | current = this.tail; 136 | count = this.length - 1 137 | while(count > index) { // OR while(count !== index) 138 | current = current.prev; 139 | count-- 140 | 141 | } 142 | } 143 | 144 | return current 145 | } 146 | 147 | set(index, value) { 148 | const foundNode = this.get(index); 149 | 150 | if(foundNode) { 151 | foundNode.value = value; 152 | return true 153 | } 154 | 155 | return false 156 | } 157 | 158 | insert(index, value) { 159 | 160 | if(index < 0 || index > this.length) { 161 | return false 162 | } 163 | 164 | if(index === this.length) { 165 | return !!this.push(value) 166 | } 167 | 168 | if(index === 0) { 169 | return !!this.unshift(value) 170 | } 171 | 172 | const newNode = new Node(value); 173 | const previous = this.get(index - 1); // 2 174 | const current = previous.next // 3 175 | 176 | previous.next = newNode; 177 | newNode.prev = previous; 178 | newNode.next = current; 179 | current.prev = newNode; 180 | this.length++ 181 | return true; 182 | } 183 | 184 | 185 | remove(index) { 186 | 187 | if(index < 0 || index >= this.length) { 188 | return null 189 | } 190 | 191 | if(index === this.length - 1) { 192 | return this.pop() 193 | } 194 | 195 | if(index === 0) { 196 | return this.shift() 197 | } 198 | 199 | const toBeRemoved = this.get(index); 200 | const previous = toBeRemoved.prev; 201 | const next = toBeRemoved.next; 202 | 203 | previous.next = next; 204 | next.prev = previous; 205 | 206 | // toBeRemoved.prev.next = toBeRemoved.next 207 | // toBeRemoved.next.prev = toBeRemoved.prev 208 | 209 | toBeRemoved.next = null; 210 | toBeRemoved.prev = null; 211 | this.length-- 212 | return toBeRemoved; 213 | } 214 | } 215 | 216 | 217 | 218 | const l = new DoublyLinkedList(); 219 | 220 | l.push(1) 221 | l.push(2) 222 | l.push(3) 223 | l.push(4) 224 | // l.push(5) 225 | // l.push(6) 226 | // a = l.unshift("hello") 227 | // let b = l.shift() 228 | // let b = l.pop() 229 | // console.log(b) 230 | // console.log(l.head, l.length) 231 | // console.log(l.insert(1, "hello")) 232 | // console.log(l.get(2)) 233 | // console.log(l.get()) 234 | // console.log(l.get(2)) 235 | console.log(l.remove(1)) 236 | // console.log(l.get(9)) 237 | console.log(l) 238 | -------------------------------------------------------------------------------- /13.Stacks.js: -------------------------------------------------------------------------------- 1 | // A LIFO (Last In First Out) data structure! 2 | // The last element added to the stack will be the first element removed from the stack 3 | 4 | // BIG O of STACKS 5 | // Insertion - O(1) 6 | // Removal - O(1) 7 | // Searching - O(N) 8 | // Access - O(N) 9 | 10 | 11 | // LINKED LIST IMPLEMENTATION 12 | 13 | class Node { 14 | constructor(value){ 15 | this.value = value 16 | this.next = null 17 | } 18 | } 19 | 20 | 21 | class Stack { 22 | constructor(){ 23 | this.first = null; // head 24 | this.last = null; // tail 25 | this.size = 0; // length 26 | } 27 | 28 | // add to the beginning - O(1) time complexity 29 | // unshift 30 | push(value) { 31 | const newNode = new Node(value); 32 | const currentFirst = this.first; 33 | newNode.next = currentFirst 34 | this.first = newNode 35 | // this.size++ 36 | 37 | // if list was empty 38 | if(!currentFirst) { 39 | this.last = newNode 40 | } 41 | 42 | return ++this.size 43 | } 44 | 45 | // remove from the beginning - O(1) time complexity 46 | // shift 47 | pop() { 48 | // if there is no element in the list 49 | if(!this.first) { 50 | return null 51 | } 52 | // if there is only one element in the list 53 | if(!this.first.next) { 54 | const currentFirst = this.first 55 | this.first = this.last = null 56 | this.size- 57 | return currentFirst.value 58 | } 59 | 60 | const currentFirst = this.first; 61 | const newFirst = currentFirst.next 62 | this.first = newFirst 63 | this.size-- 64 | return currentFirst.value 65 | } 66 | 67 | } 68 | 69 | 70 | // ARRAY IMPLEMENTATION 71 | 72 | class Stack_ { 73 | 74 | constructor() { 75 | this.stack = [] 76 | } 77 | 78 | 79 | push(element) { 80 | this.stack.push(element) 81 | } 82 | 83 | pop(element) { 84 | return this.stack.pop() 85 | } 86 | 87 | 88 | peek() { 89 | return this.stack[this.stack.length - 1] 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /14.Queue.js: -------------------------------------------------------------------------------- 1 | 2 | // A FIFO (First In First Out) data structure! 3 | 4 | // BIG O of QUEUES 5 | // Insertion - O(1) 6 | // Removal - O(1) 7 | // Searching - O(N) 8 | // Access - O(N) 9 | 10 | 11 | // LINKED LIST IMPLEMENTATION 12 | 13 | class Node { 14 | constructor(value){ 15 | this.value = value 16 | this.next = null 17 | } 18 | } 19 | 20 | 21 | class Queue { 22 | constructor(){ 23 | this.first = null; // head 24 | this.last = null; // tail 25 | this.size = 0; // length 26 | } 27 | 28 | // add to the end - O(1) time complexity 29 | // push 30 | enqueue(value) { 31 | const newNode = new Node(value) 32 | 33 | // if list is empty 34 | if(!this.first) { 35 | this.first = this.last = newNode 36 | this.size++ 37 | return this.size 38 | } 39 | 40 | // there are some existing nodes in the list 41 | const currentLast = this.last; 42 | currentLast.next = newNode 43 | this.last = newNode 44 | return ++this.size 45 | } 46 | 47 | // remove from the beginning - O(1) time complexity 48 | // shift 49 | dequeue() { 50 | // if there is no element in the list 51 | if(!this.first) { 52 | return null 53 | } 54 | // if there is only one element in the list 55 | if(!this.first.next) { 56 | const currentFirst = this.first 57 | this.first = this.last = null 58 | this.size- 59 | return currentFirst.value 60 | } 61 | 62 | const currentFirst = this.first; 63 | const newFirst = currentFirst.next 64 | this.first = newFirst 65 | this.size-- 66 | return currentFirst.value 67 | } 68 | 69 | } 70 | 71 | 72 | 73 | // ARRAY IMPLEMENTATION 74 | 75 | class Queue_ { 76 | 77 | constructor() { 78 | this.queue = [] 79 | } 80 | 81 | 82 | add(element) { 83 | this.queue.unshift(element) 84 | } 85 | 86 | remove(element) { 87 | return this.queue.pop() 88 | } 89 | 90 | } 91 | 92 | 93 | // Queue from Stack 94 | 95 | // --- Directions 96 | // Implement a Queue datastructure using two stacks. 97 | // *Do not* create an array inside of the 'Queue' class. 98 | // Queue should implement the methods 'add', 'remove', and 'peek'. 99 | // For a reminder on what each method does, look back 100 | // at the Queue exercise. 101 | // --- Examples 102 | // const q = new Queue(); 103 | // q.add(1); 104 | // q.add(2); 105 | // q.peek(); // returns 1 106 | // q.remove(); // returns 1 107 | // q.remove(); // returns 2 108 | 109 | class Stack { 110 | constructor() { 111 | this.data = []; 112 | } 113 | 114 | push(record) { 115 | this.data.push(record); 116 | } 117 | 118 | pop() { 119 | return this.data.pop(); 120 | } 121 | 122 | peek() { 123 | return this.data[this.data.length - 1]; 124 | } 125 | } 126 | 127 | 128 | // FIFO 129 | class QueueFromStack { 130 | constructor() { 131 | 132 | this.firstStack = new Stack(); 133 | this.secondStack = new Stack() 134 | } 135 | 136 | add(element) { 137 | this.firstStack.push(element) 138 | } 139 | 140 | remove() { 141 | while(this.firstStack.peek()) { 142 | this.secondStack.push(this.firstStack.pop()) 143 | } 144 | 145 | const removedItem = this.secondStack.pop() 146 | 147 | while(this.secondStack.peek()) { 148 | this.firstStack.push(this.secondStack.pop()) 149 | } 150 | 151 | return removedItem 152 | } 153 | 154 | peek() { 155 | 156 | while(this.firstStack.peek()) { 157 | this.secondStack.push(this.firstStack.pop()) 158 | } 159 | 160 | const item = this.secondStack.peek() 161 | 162 | while(this.secondStack.peek()) { 163 | this.firstStack.push(this.secondStack.pop()) 164 | } 165 | 166 | return item; 167 | } 168 | 169 | } 170 | 171 | 172 | const q = new QueueFromStack(); 173 | q.add(1); 174 | q.add(2); 175 | q.add(3); 176 | q.add(4); 177 | console.log(q.peek()); // returns 1 178 | console.log(q.remove()); // returns 1 179 | console.log(q.remove()); // returns 2 180 | console.log(q.peek()); // returns 3 181 | -------------------------------------------------------------------------------- /15.BST.js: -------------------------------------------------------------------------------- 1 | // WHAT IS A TREE? 2 | // A data structure that consists of nodes in a parent / child relationship 3 | // Lists - linear 4 | // Trees - nonlinear 5 | 6 | // TREE TERMINOLOGY 7 | // Root - The top node in a tree. 8 | // Child -A node directly connected to another node when moving away from the Root. 9 | // Parent - The converse notion of a child. 10 | // Siblings -A group of nodes with the same parent. 11 | // Leaf - A node with no children. 12 | // Edge - The connection(arrow) between one node and another. 13 | 14 | // Lots of different applications! 15 | // HTML DOM 16 | // Network Routing 17 | // Abstract Syntax Tree 18 | // Artificial Intelligence 19 | // Folders in Operating Systems 20 | // Computer File Systems 21 | 22 | // Binary trees are special case of trees where each node can have a maximum of 2 children; 0, 1 or 2 23 | // Binary Search Trees(BSTs) are special case of Binary trees and are sorted in a particular way, 24 | // ensuring that every node's left hand child is less than the parent node's value, and that 25 | // every node's right hand child is greater than the parent 26 | 27 | // Big O of BST 28 | // Insertion - O(log n) 29 | // Searching - O(log n) 30 | // O(log n) : Double the number of nodes, You only increase the number of steps to insert/find by 1 31 | // 2x number of nodes: 1 extra step 32 | // 4x number of nodes: 2 extra steps 33 | // 8x number of nodes: 3 extra steps 34 | // *NOT guaranteed! 35 | 36 | // Tree Traversal - Visit every node once 37 | 38 | // Two ways: 39 | // Breadth-first Search (BFS) 40 | // Depth-first Search (DFS) -> InOrder, PreOrder and PostOrder 41 | 42 | // visit the node, traverse the left and traverse the right 43 | // PreOrder -> visit the node first(i.e, add it to the list), then traverse the entire left and then traverse the entire right (NLR) 44 | // PostOrder -> visit the node after. Traverse the left and the right and then visit the node(i.e, add it to the list) (LRN) 45 | // InOrder -> traverse the entire left side, then visit the node(i.e, add it to the list) and then traverse the entire right (LNR) 46 | 47 | // DFS uses a stack (push and pop) and BFS uses a queue(push and shift) 48 | 49 | class Node { 50 | constructor(value) { 51 | this.value = value; 52 | this.left = null; 53 | this.right = null; 54 | } 55 | } 56 | 57 | class BinarySearchTree { 58 | constructor() { 59 | this.root = null; 60 | } 61 | 62 | insert(value) { 63 | const newNode = new Node(value); 64 | let current = this.root; 65 | 66 | if (!current) { 67 | this.root = newNode; 68 | return this; 69 | } 70 | 71 | while (true) { 72 | if (value === current.value) { 73 | return null; 74 | } 75 | 76 | // check if the value is greater than the current node value 77 | if (value > current.value) { 78 | // check if there is a right value 79 | if (current.right) { 80 | current = current.right; 81 | } else { 82 | // if there is not a right node, then insert the node 83 | current.right = newNode; 84 | return this; 85 | } 86 | } else if (value < current.value) { 87 | // check if there is a left value 88 | if (current.left) { 89 | current = current.left; 90 | } else { 91 | current.left = newNode; 92 | return this; 93 | } 94 | } 95 | } 96 | } 97 | 98 | insertRecursive(root, val) { 99 | if (!root) { 100 | return new Node(value); 101 | } 102 | 103 | if (val > root.value) { 104 | root.right = this.insertRecursive(root.right, val); 105 | } else if (val < root.value) { 106 | root.left = this.insertRecursive(root.left, val); 107 | } 108 | 109 | return root; 110 | } 111 | 112 | find(value) { 113 | let current = this.root; 114 | 115 | while (current) { 116 | if (value > current.value) { 117 | current = current.right; 118 | } else if (value < current.value) { 119 | current = current.left; 120 | } else { 121 | // value = current.value 122 | return current; 123 | } 124 | } 125 | 126 | return null; 127 | } 128 | 129 | // recursive 130 | contains(value, root = this.root) { 131 | if (root === null) return false; 132 | if (value === root.value) return true; 133 | return ( 134 | this.contains(value, root.left) || 135 | this.contains(value, root.right) 136 | ); 137 | } 138 | 139 | minValueNode() { 140 | let current = this.root; 141 | if (!current) return null; 142 | 143 | while (current && current.left) { 144 | current = current.left; 145 | } 146 | 147 | return current; 148 | } 149 | 150 | minValueOfASubtree(node) { 151 | if (!node) return null; 152 | 153 | while (node && node.left) { 154 | node = node.left; 155 | } 156 | 157 | return node; 158 | } 159 | 160 | findMaxValueOfASubtree(node) { 161 | if (!node) return null; 162 | 163 | while (node && node.right) { 164 | node = node.right; 165 | } 166 | 167 | return node; 168 | } 169 | 170 | remove(root, value) { 171 | if (!root) return null; 172 | 173 | if (value < root.value) { 174 | root.left = this.remove(root.left, value); 175 | } else if (value > root.value) { 176 | root.right = this.remove(root.right, value); 177 | } else { 178 | // Node with only one child or no child 179 | if (!root.left) { 180 | return root.right; 181 | } else if (!root.right) { 182 | return root.left; 183 | } else { 184 | // Node with two children: Get the inorder successor (smallest in the right subtree) 185 | const minNodeVal = this.minValueOfASubtree(root.right); 186 | 187 | // Copy the inorder successor's content to this node 188 | root.value = minNodeVal.value; 189 | 190 | // Delete the inorder successor 191 | root.right = this.remove(root.right, minNodeVal.value); 192 | } 193 | } 194 | 195 | return root; 196 | } 197 | 198 | removeIterative(value) { 199 | let current = this.root; 200 | let parent = null; 201 | 202 | while (current) { 203 | if (value < current.value) { 204 | parent = current; 205 | current = current.left; 206 | } else if (value > current.value) { 207 | parent = current; 208 | current = current.right; 209 | } else { 210 | // Node with only one child or no child 211 | if (!current.left) { 212 | if (parent) { 213 | parent.left = current.right; 214 | } else { 215 | this.root = current.right; 216 | } 217 | } else if (!current.right) { 218 | if (parent) { 219 | parent.left = current.left; 220 | } else { 221 | this.root = current.left; 222 | } 223 | } else { 224 | // Node with two children: Get the inorder successor 225 | const minNodeVal = this.minValueOfASubtree(current.right); 226 | current.value = minNodeVal.value; 227 | this.removeIterative(minNodeVal.value); 228 | } 229 | return true; 230 | } 231 | } 232 | 233 | return false; 234 | } 235 | 236 | // Level Order Traversal 237 | BFS() { 238 | let current = this.root; 239 | if (!current) return null; 240 | 241 | const queue = [current]; 242 | const results = []; 243 | 244 | while (queue.length) { 245 | let node = queue.shift(); 246 | results.push(node); 247 | if (node.left) queue.push(node.left); 248 | if (node.right) queue.push(node.right); 249 | } 250 | 251 | return results; 252 | } 253 | 254 | DFSPreOrderIterative() { 255 | let current = this.root; 256 | if (!current) return null; 257 | 258 | const stack = [current]; 259 | const results = []; 260 | 261 | while (stack.length) { 262 | let node = stack.shift(); 263 | results.push(node.value); 264 | if (node.right) stack.unshift(node.right); 265 | if (node.left) stack.unshift(node.left); 266 | } 267 | 268 | return results; 269 | } 270 | 271 | DFSPreOrder() { 272 | if (!this.root) return null; 273 | 274 | const results = []; 275 | 276 | function traverse(node) { 277 | results.push(node); 278 | if (node.left) traverse(node.left); 279 | if (node.right) traverse(node.right); 280 | } 281 | 282 | traverse(this.root); 283 | return results; 284 | } 285 | 286 | DFSPostOrderIterative() { 287 | let current = this.root; 288 | if (!current) return null; 289 | 290 | const stack = [current]; 291 | const results = []; 292 | 293 | while (stack.length) { 294 | let node = stack.pop(); 295 | results.unshift(node.value); 296 | if (node.left) stack.push(node.left); 297 | if (node.right) stack.push(node.right); 298 | } 299 | 300 | return results; 301 | } 302 | 303 | DFSPostOrder() { 304 | if (!this.root) return null; 305 | 306 | const results = []; 307 | 308 | function traverse(node) { 309 | if (node.left) traverse(node.left); 310 | if (node.right) traverse(node.right); 311 | results.push(node); 312 | } 313 | 314 | traverse(this.root); 315 | return results; 316 | } 317 | 318 | DFSInOrderIterative() { 319 | let current = this.root; 320 | if (!current) return null; 321 | 322 | const stack = []; 323 | const results = []; 324 | 325 | while (true) { 326 | if (current !== null) { 327 | stack.push(current); 328 | current = current.left; 329 | } else { 330 | if (stack.length === 0) { 331 | break; 332 | } 333 | current = stack.pop(); 334 | results.push(current.value); 335 | current = current.right; 336 | } 337 | } 338 | 339 | return results; 340 | } 341 | 342 | DFSInOrder() { 343 | if (!this.root) return null; 344 | 345 | const results = []; // this will be an array of node values in sorted order i.e from smallest to largest 346 | // inorder traversal gives us sorted order of the nodes in a BST 347 | // i.e, if we do an inorder traversal of a BST, we will get the values in sorted order 348 | 349 | function traverse(node) { 350 | if (node.left) traverse(node.left); 351 | results.push(node.value); 352 | if (node.right) traverse(node.right); 353 | } 354 | 355 | traverse(this.root); 356 | return results; 357 | } 358 | 359 | // OR 360 | // DFSInOrder2(root) { 361 | // if (!root) return null; 362 | 363 | // this.DFSInOrder2(root.left); 364 | // console.log(root.value); 365 | // this.DFSInOrder2(root.right); 366 | // } 367 | 368 | printAllLeafs(root = this.root) { 369 | if (root === null) return null; 370 | 371 | if (root.left === null && root.right === null) { 372 | console.log(root.value); 373 | } 374 | 375 | if (root.left !== null) { 376 | this.printAllLeafs(root.left); 377 | } 378 | 379 | if (root.right !== null) { 380 | this.printAllLeafs(root.right); 381 | } 382 | } 383 | 384 | sumAll(node = this.root) { 385 | if (node === null) return 0; 386 | 387 | return ( 388 | node.value + this.sumAll(node.left) + this.sumAll(node.right) 389 | ); 390 | } 391 | 392 | maxRootToLeafSum(root = this.root) { 393 | if (root === null) return -Infinity; 394 | if (root.left === null && root.right === null) return root.value; 395 | return ( 396 | root.value + 397 | Math.max( 398 | this.maxRootToLeafSum(root.left), 399 | this.maxRootToLeafSum(root.right) 400 | ) 401 | ); 402 | } 403 | } 404 | 405 | // 10 406 | // 5 13 407 | // 2 7 11 16 408 | 409 | var tree = new BinarySearchTree(); 410 | tree.insert(10); 411 | tree.insert(5); 412 | tree.insert(13); 413 | tree.insert(11); 414 | tree.insert(2); 415 | tree.insert(16); 416 | tree.insert(10); 417 | tree.insert(7); 418 | console.log(tree.contains(11)); 419 | console.log(tree.sumAll()); 420 | console.log(tree.maxRootToLeafSum()); // 39 421 | // tree.printAllLeafs() // [2, 7, 11, 16] 422 | // console.log(tree.find(13)) 423 | // console.log(tree.BFS()) // [10, 5, 13, 2, 7, 11, 16] 424 | // console.log(tree.DFSPreOrder()) // [10, 5, 2, 7, 13, 11, 16] -> Can be used to "export" a tree structure 425 | // so that it is easily reconstructed or copied 426 | // console.log(tree.DFSPostOrder()) // [2, 7, 5, 11, 16, 13, 10] 427 | // console.log(tree.DFSInOrder()) // [2, 5, 7, 10, 11, 13, 16] -> Notice we get all nodes in the tree in their underlying order 428 | 429 | // Validate if Binary Search Tree is Valid; 430 | let isValidBST = function (root, min = -Infinity, max = Infinity) { 431 | if (root.val >= max) return false; 432 | if (root.val <= min) return false; 433 | 434 | if (root.left) { 435 | if (!isValidBST(root.left, min, root.val)) { 436 | return false; 437 | } 438 | } 439 | 440 | if (root.right) { 441 | if (!isValidBST(root.right, root.val, max)) { 442 | return false; 443 | } 444 | } 445 | 446 | return true; 447 | }; 448 | 449 | // OR 450 | 451 | let isValidBST2 = function (tree, min = -Infinity, max = Infinity) { 452 | if (tree === null) { 453 | return true; 454 | } 455 | 456 | if (tree.val < min || tree.val > max) { 457 | return false; 458 | } 459 | 460 | let isLeftValid = isValidBST2(tree.left, min, tree.val); 461 | let isRightValid = isValidBST2(tree.right, tree.val, max); 462 | 463 | return isLeftValid && isRightValid; 464 | }; 465 | 466 | // Invert a binary tree -> Given the root of a binary tree, invert the tree, and return its root. 467 | let invertTree = function (root) { 468 | if (root === null) { 469 | return root; 470 | } 471 | 472 | let left = root.left; 473 | let right = root.right; 474 | 475 | root.left = right; 476 | root.right = left; 477 | 478 | invertTree(root.left); 479 | invertTree(root.right); 480 | 481 | return root; 482 | }; 483 | 484 | let invertTreeIterative = function (root) { 485 | let queue = [root]; 486 | 487 | while (queue.length > 0) { 488 | let current = queue.shift(); 489 | 490 | if (current === null) { 491 | continue; 492 | } 493 | 494 | // swap left and right; 495 | let left = current.left; 496 | current.left = current.right; 497 | current.right = left; 498 | 499 | queue.push(current.left); 500 | queue.push(current.right); 501 | } 502 | }; 503 | -------------------------------------------------------------------------------- /16.BinaryHeap.js: -------------------------------------------------------------------------------- 1 | // WHAT IS A BINARY HEAP? 2 | // Very similar to a binary search tree, but with some different rules! 3 | // Each node can have a maximum of 2 children; 0, 1 or 2 4 | // In a MaxBinaryHeap, parent nodes are always larger than child nodes. 5 | // In a MinBinaryHeap, parent nodes are always smaller than child nodes 6 | 7 | // MAX BINARY HEAP 8 | // Each parent has at most two child nodes 9 | // The value of each parent node is always greater than its child nodes 10 | // So recursively, we want every value in the left subtree to smaller than the parent node 11 | // and we want every value in the right subtree to be smaller than the parent node. 12 | // In a max Binary Heap the parent is greater than the children, but there are no guarantees between sibling nodes. 13 | // A binary heap is as compact as possible. All the children of each node are as full 14 | // as they can be and left children are filled out first. 15 | 16 | // Why do we need to know this? 17 | // Binary Heaps are used to implement Priority Queues, which are very commonly used data structures 18 | // They are also used quite a bit, with graph traversal algorithms 19 | 20 | // THERE'S AN EASY WAY OF STORING A BINARY HEAP...A LIST OR AN ARRAY 21 | // With just a little bit of math, we can represent heaps using arrays! 22 | 23 | // For any index of an array n... 24 | 25 | // If you have a parent node at index n and want to find child nodes: 26 | // The left child is stored at 2n + 1 index in the array 27 | // The right child is at 2n + 2 28 | // WHAT IF WE HAVE A CHILD NODE AND WANT TO FIND ITS PARENT? 29 | // For any child node at index n... 30 | // Its parent is at index Math.floor((n-1)/2) 31 | // This is if we are using a 0-based index 32 | 33 | // If we use a 1-based index, i.e the first element(root of the heap) is at index 1, 34 | // The left child is stored at 2n index in the array 35 | // The right child is at 2n + 1 36 | // And given a child node at index n, we can find its parent node at index Math.floor(n/2). 37 | 38 | // 100 39 | // 19 36 40 | // 17 12 25 5 41 | // 9 15 16 11 13 8 1 4 42 | // 43 | // [100, 19, 36, 17, 12, 25, 5, 9, 15, 16, 11, 13, 8, 1, 4] 44 | 45 | // We fill in the left first and then the right 46 | 47 | // Instead of creating bunch of nodes and storing them manually like we did in BST, instead 48 | // we store them in an array and use their position in the array to model their actual structure. 49 | // The index, individual numbers corrsponding to each item's positionis what actually gives that the structure of the heap. 50 | 51 | // Each time we go down a step in bianry heap, we have two times the number of nodes 52 | // Everytime we double the number of nodes, i.e, one full layer, we are only increasing the time it takes by one. 53 | 54 | // Big O of Binary Heaps 55 | // Insertion - O(log N) 56 | // Removal - O(log N) 57 | // Search - O(N) 58 | // Get Max/Min value - O(1) 59 | 60 | // 41 61 | // 39 33 62 | // 18 27 12 63 | // [41, 39, 33, 18, 27, 12] 64 | 65 | class MaxBinaryHeap { 66 | constructor(arr) { 67 | // this.values = [41, 39, 33, 18, 27, 12] 68 | this.values = this.buildHeap(arr); 69 | } 70 | 71 | insert(value) { 72 | // add it to the end 73 | this.values.push(value); 74 | 75 | // bubble the inserted value up to it's correct spot 76 | this.bubbleUp(); 77 | } 78 | 79 | bubbleUp() { 80 | let currentIndex = this.values.length - 1; 81 | let currentValue = this.values[currentIndex]; 82 | 83 | while (currentIndex > 0) { 84 | let parentIndex = Math.floor((currentIndex - 1) / 2); 85 | let parentValue = this.values[parentIndex]; 86 | 87 | if (currentValue > parentValue) { 88 | // swap the values 89 | this.values[currentIndex] = parentValue; 90 | this.values[parentIndex] = currentValue; 91 | 92 | currentIndex = parentIndex; 93 | } else { 94 | // inserted element is in the right spot. So, break out of loop 95 | break; 96 | } 97 | } 98 | } 99 | 100 | extractMax() { 101 | // Remove the root 102 | const max = this.values[0]; 103 | const last = this.values.pop(); 104 | 105 | // edge case 106 | if (this.values.length > 0) { 107 | // Replace with the most recently added 108 | this.values[0] = last; 109 | 110 | // Adjust (sink down) 111 | this.sinkDown(); 112 | } 113 | 114 | return max; 115 | } 116 | 117 | sinkDown() { 118 | let parentIndex = 0; 119 | let parentValue = this.values[parentIndex]; 120 | 121 | while (true) { 122 | let leftChildIndex = 2 * parentIndex + 1; 123 | let rightChildIndex = 2 * parentIndex + 2; 124 | let leftChildValue, rightChildValue; 125 | let swap = null; 126 | let swapIndex = null; 127 | 128 | if (leftChildIndex < this.values.length) { 129 | leftChildValue = this.values[leftChildIndex]; 130 | 131 | if (leftChildValue > parentValue) { 132 | swap = leftChildValue; 133 | swapIndex = leftChildIndex; 134 | } 135 | } 136 | 137 | if (rightChildIndex < this.values.length) { 138 | rightChildValue = this.values[rightChildIndex]; 139 | 140 | if ( 141 | (swap === null && rightChildValue > parentValue) || 142 | (swap !== null && rightChildValue > swap) 143 | ) { 144 | swap = rightChildValue; 145 | swapIndex = rightChildIndex; 146 | } 147 | } 148 | 149 | if (swap) { 150 | this.values[parentIndex] = swap; 151 | this.values[swapIndex] = parentValue; 152 | parentIndex = swapIndex; 153 | } else { 154 | break; 155 | } 156 | } 157 | } 158 | 159 | // O(logn) time | O(1) space 160 | siftDown(parentIndex, arr) { 161 | let endIndex = arr.length - 1; 162 | let leftChildIndex = 2 * parentIndex + 1; 163 | let swapIndex; 164 | 165 | while (leftChildIndex <= endIndex) { 166 | let rightChildIndex = 167 | 2 * parentIndex + 2 <= endIndex ? 2 * parentIndex + 2 : -1; 168 | 169 | if ( 170 | rightChildIndex !== -1 && 171 | arr[rightChildIndex] > arr[leftChildIndex] 172 | ) { 173 | swapIndex = rightChildIndex; 174 | } else { 175 | swapIndex = leftChildIndex; 176 | } 177 | 178 | if (arr[swapIndex] > arr[parentIndex]) { 179 | let larger = arr[swapIndex]; 180 | arr[swapIndex] = arr[parentIndex]; 181 | arr[parentIndex] = larger; 182 | 183 | parentIndex = swapIndex; 184 | leftChildIndex = 2 * parentIndex + 1; 185 | } else { 186 | break; 187 | } 188 | } 189 | } 190 | 191 | // To heapify a subtree rooted with node i which is 192 | // an index in arr[]. n is size of heap 193 | // O(logn) time comolexity 194 | heapify(arr, n, i) { 195 | var largest = i; // Initialize largest as root 196 | var l = 2 * i + 1; // left = 2*i + 1 197 | var r = 2 * i + 2; // right = 2*i + 2 198 | 199 | // If left child is larger than root 200 | if (l < n && arr[l] > arr[largest]) largest = l; 201 | 202 | // If right child is larger than largest so far 203 | if (r < n && arr[r] > arr[largest]) largest = r; 204 | 205 | // If largest is not root 206 | if (largest != i) { 207 | let swap = arr[i]; 208 | arr[i] = arr[largest]; 209 | arr[largest] = swap; 210 | 211 | // Recursively heapify the affected sub-tree 212 | this.heapify(arr, n, largest); 213 | } 214 | } 215 | 216 | // O(n) time complexity 217 | buildHeapRecursive(arr) { 218 | let n = arr.length; 219 | for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { 220 | this.heapify(arr, n, i); 221 | } 222 | 223 | return arr; 224 | } 225 | 226 | buildHeap(arr) { 227 | let n = arr.length; 228 | let firstParentIdx = Math.floor(n / 2) - 1; 229 | 230 | for (let i = firstParentIdx; i >= 0; i--) { 231 | this.siftDown(i, arr); 232 | } 233 | 234 | return arr; 235 | } 236 | 237 | peek() { 238 | return this.values[0]; 239 | } 240 | } 241 | 242 | // SINK DOWN ? 243 | // The procedure for deleting the root from the heap (effectively extracting the maximum element in 244 | // a max-heap or the minimum element in a min-heap) and restoring the properties 245 | // is called down-heap (also known as bubble-down, percolate-down, sift-down, trickle down, 246 | // heapify-down, cascade-down, and extract-min/max). 247 | 248 | const heap = new MaxBinaryHeap([1, 2, 3, 4, 5]); 249 | console.log(heap.values); // [ 5, 4, 3, 1, 2 ] 250 | heap.insert(55); 251 | heap.insert(23); 252 | console.log(heap.values); // [55, 4, 23, 1, 2, 3, 5] 253 | console.log(heap.extractMax()); 254 | console.log(heap.values); // [ 23, 4, 5, 1, 2, 3 ] 255 | -------------------------------------------------------------------------------- /17.PriorityQueue.js: -------------------------------------------------------------------------------- 1 | // WHAT IS A PRIORITY QUEUE? 2 | 3 | // A data structure where each element has a priority. 4 | // Elements with higher priorities are served before elements with lower priorities. 5 | 6 | // OUR PRIORITY QUEUE 7 | 8 | // Write a Min Binary Heap - lower number means higher priority. 9 | // Each Node has a val and a priority. Use the priority to build the heap. 10 | // Enqueue method accepts a value and priority, makes a new node, and puts it in the right spot based off of its priority. 11 | // Dequeue method removes root element, returns it, and rearranges heap using priority 12 | 13 | 14 | class Node { 15 | constructor(value, priority){ 16 | this.value = value; 17 | this.priority = priority; // greater the priority number, higher is the priority 18 | } 19 | } 20 | 21 | 22 | // MinBinaryHeap 23 | class PriorityQueue { 24 | constructor() { 25 | this.values = []; 26 | } 27 | 28 | enqueue(value, priority) { 29 | const node = new Node(value, priority); 30 | this.values.push(node); 31 | 32 | this.bubbleUp(); 33 | } 34 | 35 | 36 | bubbleUp() { 37 | let currentIdx = this.values.length - 1; 38 | let currentValue = this.values[currentIdx]; 39 | 40 | while(currentIdx > 0) { 41 | let parentIdx = Math.floor((currentIdx - 1) / 2) 42 | let parentValue = this.values[parentIdx]; 43 | 44 | if(currentValue.priority > parentValue.priority) { 45 | this.values[parentIdx] = currentValue; 46 | this.values[currentIdx] = parentValue; 47 | currentIdx = parentIdx; 48 | } else { 49 | break; 50 | } 51 | } 52 | } 53 | 54 | dequeue() { 55 | const max = this.values[0]; 56 | const last = this.values.pop(); 57 | 58 | // edge case 59 | if(this.values.length > 0) { 60 | this.values[0] = last; 61 | this.sinkDown(); 62 | } 63 | 64 | return max; 65 | } 66 | 67 | 68 | sinkDown() { 69 | let parentIdx = 0 70 | let parentValue = this.values[parentIdx]; 71 | const length = this.values.length; 72 | 73 | while(true) { 74 | let leftChildIdx = (2 * parentIdx) + 1; 75 | let rightChildIdx = (2 * parentIdx) + 2; 76 | let leftChildValue, rightChildValue; 77 | let swapIndex = null; 78 | let swapValue = null; 79 | 80 | if(leftChildIdx < length) { 81 | leftChildValue = this.values[leftChildIdx] 82 | 83 | if(leftChildValue.priority > parentValue.priority) { 84 | swapIndex = leftChildIdx; 85 | swapValue = leftChildValue 86 | } 87 | } 88 | 89 | if(rightChildIdx < length) { 90 | rightChildValue = this.values[rightChildIdx] 91 | 92 | if((swapValue === null && rightChildValue.priority > parentValue.priority) 93 | || // leftChildValue.priority 94 | (swapValue !== null && rightChildValue.priority > swapValue.priority)) { 95 | swapIndex = rightChildIdx; 96 | swapValue = rightChildValue 97 | } 98 | } 99 | 100 | if(swapValue !== null) { 101 | this.values[parentIdx] = swapValue; 102 | this.values[swapIndex] = parentValue 103 | 104 | parentIdx = swapIndex 105 | } else { 106 | break; 107 | } 108 | } 109 | } 110 | } 111 | 112 | let ER = new PriorityQueue(); 113 | 114 | ER.enqueue("common cold", 5) 115 | ER.enqueue("gunshot wound", 1) 116 | ER.enqueue("high fever", 4) 117 | ER.enqueue("broken arm", 2) 118 | ER.enqueue("glass in foot", 3) 119 | 120 | console.log(ER.dequeue()) // 5 121 | console.log(ER.dequeue()) // 4 122 | console.log(ER.dequeue()) // 3 123 | console.log(ER.dequeue()) // 2 124 | console.log(ER.dequeue()) // 1 125 | 126 | console.log(ER.values) 127 | 128 | 129 | // Simple or Naive Version. 130 | // We are sorting which is O(n*logn) 131 | // class PriorityQueue { 132 | // 133 | // constructor(){ 134 | // this.values = []; 135 | // } 136 | // 137 | // enqueue(val, priority) { 138 | // this.values.push({val, priority}); 139 | // this.sort(); 140 | // }; 141 | // 142 | // dequeue() { 143 | // return this.values.shift(); 144 | // }; 145 | // 146 | // sort() { 147 | // this.values.sort((a, b) => a.priority - b.priority); 148 | // }; 149 | // 150 | // } 151 | -------------------------------------------------------------------------------- /18.HashTable.js: -------------------------------------------------------------------------------- 1 | // WHAT IS A HASH TABLE / HASH MAP 2 | 3 | // Hash tables are used to store key-value pairs. 4 | // They are like arrays, but the keys are not ordered. 5 | // Unlike arrays, hash tables are fast for all of the following operations: 6 | // finding values, adding new values, and removing value 7 | 8 | // THE HASH PART 9 | // To implement a hash table, we'll be using an array. 10 | // In order to look up values by key, we need a way to convert keys into valid array indices. 11 | // A function that performs this task is called a hash function. 12 | 13 | // WHAT MAKES A GOOD HASH? 14 | // Fast (i.e. constant time) 15 | // Doesn't cluster outputs at specific indices, but distributes uniformly 16 | // Deterministic (same input yields same output) 17 | 18 | // Prime numbers? wut. 19 | // The prime number in the hash is helpful in spreading out the keys more uniformly. 20 | // It's also helpful if the array that you're putting values into has a prime length. 21 | // You don't need to know why. (Math is complicated!). 22 | // Why do hash functions use prime numbers? 23 | // Does making array size a prime number help in hash table implementation? 24 | 25 | // Dealing with Collisions 26 | // Even with a large array and a great hash function, collisions are inevitable. 27 | // There are many strategies for dealing with collisions, but we'll focus on two: 28 | // Separate Chaining 29 | // Linear Probing 30 | 31 | // Separate chaining and linear probing are two strategies used to deal with two keys that hash to the same index 32 | 33 | // Separate Chaining 34 | // With separate chaining, at each index in our array we store values using a more 35 | // sophisticated data structure (e.g. an array or a linked list). 36 | // This allows us to store multiple key-value pairs at the same index. 37 | 38 | // Linear Probing | Open Addressing 39 | // With linear probing, when we find a collision, we search through the array to find the next empty slot. 40 | // Unlike with separate chaining, this allows us to store a single key-value at each index. 41 | 42 | // BIG O of HASH TABLES 43 | // Insert - O(1) 44 | // Deletion - O(1) 45 | // Access - O(1) 46 | 47 | class HashTable { 48 | constructor(size = 53) { 49 | this.keyMap = new Array(size); 50 | } 51 | 52 | _hash(key) { 53 | let total = 0; 54 | let WEIRD_PRIME = 31; 55 | for (let i = 0; i < Math.min(key.length, 100); i++) { 56 | let char = key[i]; 57 | let value = char.charCodeAt(0) - 96; 58 | total = (total * WEIRD_PRIME + value) % this.keyMap.length; 59 | } 60 | return total; 61 | } 62 | 63 | set(key, value) { 64 | let index = this._hash(key); 65 | if (!this.keyMap[index]) { 66 | this.keyMap[index] = []; 67 | } 68 | this.keyMap[index].push([key, value]); 69 | } 70 | 71 | get(key) { 72 | let index = this._hash(key); 73 | if (this.keyMap[index]) { 74 | for (let i = 0; i < this.keyMap[index].length; i++) { 75 | if (this.keyMap[index][i][0] === key) { 76 | return this.keyMap[index][i][1]; 77 | } 78 | } 79 | } 80 | return undefined; 81 | } 82 | 83 | keys() { 84 | let keysArr = []; 85 | for (let i = 0; i < this.keyMap.length; i++) { 86 | if (this.keyMap[i]) { 87 | for (let j = 0; j < this.keyMap[i].length; j++) { 88 | if (!keysArr.includes(this.keyMap[i][j][0])) { 89 | keysArr.push(this.keyMap[i][j][0]); 90 | } 91 | } 92 | } 93 | } 94 | return keysArr; 95 | } 96 | 97 | values() { 98 | let valuesArr = []; 99 | for (let i = 0; i < this.keyMap.length; i++) { 100 | if (this.keyMap[i]) { 101 | for (let j = 0; j < this.keyMap[i].length; j++) { 102 | if (!valuesArr.includes(this.keyMap[i][j][1])) { 103 | valuesArr.push(this.keyMap[i][j][1]); 104 | } 105 | } 106 | } 107 | } 108 | return valuesArr; 109 | } 110 | } 111 | 112 | let ht = new HashTable(17); 113 | ht.set('maroon', '#800000'); 114 | ht.set('yellow', '#FFFF00'); 115 | ht.set('olive', '#808000'); 116 | ht.set('salmon', '#FA8072'); 117 | ht.set('lightcoral', '#F08080'); 118 | ht.set('mediumvioletred', '#C71585'); 119 | ht.set('plum', '#DDA0DD'); 120 | ht.set('purple', '#DDA0DD'); 121 | ht.set('violet', '#DDA0DD'); 122 | 123 | ht.keys().forEach(function (key) { 124 | console.log(ht.get(key)); 125 | }); 126 | -------------------------------------------------------------------------------- /19.Graphs.js: -------------------------------------------------------------------------------- 1 | // WHAT ARE GRAPHS 2 | // A graph data structure consists of a finite (and possibly mutable) set of vertices or nodes or points, 3 | // together with a set of unordered pairs of these vertices for an undirected graph 4 | // or a set of ordered pairs for a directed graph. 5 | 6 | // WHAT 7 | // Nodes(verices) + Connections(edges) 8 | 9 | // ESSENTIAL GRAPH TERMS 10 | // Vertex - a node 11 | // Edge - connection between nodes 12 | // Weighted/Unweighted - values assigned to distances between vertices 13 | // Directed/Undirected - directions assigned to distanced between vertices 14 | 15 | // Tree is a graph in which any two vertices are connected by exactly only one path. 16 | 17 | // How do we store/represent a graph ? 18 | // 1. Adjacent Matrix 19 | // 2. Adjacency List 20 | // 3. Edge List 21 | 22 | 23 | // DIFFERENCES & BIG O OPERATION ADJACENCY LIST ADJACENCY MATRIX: 24 | // |V| - number of vertices 25 | // |E| - number of edges 26 | 27 | // OPERATION AdjacencyList AdjacencyMatrix 28 | 29 | // Add Vertex O(1) ​O(|V^2|) 30 | // Add Edge O(1) O(1) 31 | // Remove Vertex O(|V| + |E|) ​O(|V^2|) 32 | // Remove Edge O(|E|) O(1) 33 | // Query O(|V| + |E|) O(1) 34 | // Storage O(|V| + |E|) ​ O(|V^2|) 35 | 36 | // 1. Adjacency List 37 | // Can take up less space (in sparse graphs) 38 | // Faster to iterate over all edges 39 | // Can be slower to lookup specific edge 40 | 41 | // 2. Adjacency Matrix 42 | // Takes up more space (in sparse graphs) 43 | // Slower to iterate over all edges 44 | // Faster to lookup specific edge 45 | 46 | // 3. Edge List 47 | // An edge list is a way to represent a graph 48 | // simply as an unordered list of edges. Assume 49 | // the notation for any triplet (u,v,w) means: 50 | // “the cost from node u to node v is w 51 | // [(C,A,4), (A,C,1),(B,C,6), (A,B,4), (C,B,1), (C,D,2)] 52 | // Space efficient for representing sparse graphs 53 | // Less space efficient for denser graphs. 54 | // Iterating over all edges is efficient Edge weight lookup is O(E) 55 | // Very simple structure 56 | 57 | // Graph Traversal - Visiting/Updating/Checking each vertex in a graph 58 | // 1. DEPTH FIRST: Explore as far as possible down one branch before "backtracking" 59 | // 2. BREADTH FIRST: Visit neighbors at current depth first! 60 | 61 | // Undirected Graph 62 | class Graph { 63 | 64 | constructor () { 65 | this.adjacencyList = {} 66 | 67 | // { 68 | // vertexName: [list of all the vertices this vertex is linked/connected to] 69 | // } 70 | 71 | // { 72 | // A: [ 'B', 'C' ], 73 | // B: [ 'A', 'D' ], 74 | // C: [ 'A', 'E' ], 75 | // D: [ 'B', 'E', 'F' ], 76 | // E: [ 'C', 'D', 'F' ], 77 | // F: [ 'D', 'E' ] 78 | // } 79 | 80 | } 81 | 82 | addVertex (vertex) { 83 | if (!this.adjacencyList[vertex]) { 84 | this.adjacencyList[vertex] = [] 85 | } 86 | } 87 | 88 | addEdge (vertex1, vertex2) { 89 | if (this.adjacencyList[vertex1] && this.adjacencyList[vertex2]) { 90 | this.adjacencyList[vertex1].push(vertex2) 91 | this.adjacencyList[vertex2].push(vertex1) 92 | } 93 | } 94 | 95 | removeEdge (vertex1, vertex2) { 96 | if (this.adjacencyList[vertex1] && this.adjacencyList[vertex2]) { 97 | this.adjacencyList[vertex1] = this.adjacencyList[vertex1].filter((vertex) => vertex !== vertex2) 98 | 99 | this.adjacencyList[vertex2] = this.adjacencyList[vertex2].filter((vertex) => vertex !== vertex1) 100 | } 101 | } 102 | 103 | removeVertex (vertex) { 104 | if (!this.adjacencyList[vertex]) { 105 | return 106 | } 107 | 108 | this.adjacencyList[vertex].forEach((adjacentVertex) => { 109 | this.adjacencyList[adjacentVertex] = 110 | this.adjacencyList[adjacentVertex].filter((v) => v !== vertex) 111 | }) 112 | 113 | delete this.adjacencyList[vertex] 114 | } 115 | 116 | // removeVertex(vertex){ 117 | // while(this.adjacencyList[vertex].length){ 118 | // const adjacentVertex = this.adjacencyList[vertex].pop(); 119 | // this.removeEdge(vertex, adjacentVertex); 120 | // } 121 | // delete this.adjacencyList[vertex] 122 | // } 123 | 124 | depthFirstRecursive (start) { 125 | const results = [] 126 | const visited = [] 127 | const adjacencyList = this.adjacencyList 128 | 129 | const dfs = (vertex) => { 130 | if (!vertex) { 131 | return null 132 | } 133 | 134 | visited[vertex] = true 135 | results.push(vertex) 136 | 137 | adjacencyList[vertex].forEach((neighbour) => { 138 | if (!visited[neighbour]) { 139 | dfs(neighbour) 140 | } 141 | }) 142 | } 143 | 144 | dfs(start) 145 | return results 146 | } 147 | 148 | depthFirstIterative (start) { 149 | const results = [] 150 | const visited = [] 151 | const stack = [start] 152 | const adjacencyList = this.adjacencyList 153 | 154 | visited[start] = true; 155 | while (stack.length) { 156 | let vertex = stack.pop() 157 | results.push(vertex) 158 | 159 | adjacencyList[vertex].forEach((neighbour) => { 160 | if (!visited[neighbour]) { 161 | visited[neighbour] = true 162 | stack.push(neighbour) 163 | } 164 | }) 165 | } 166 | 167 | return results; 168 | } 169 | 170 | breadthFirstIterative(start) { 171 | const results = [] 172 | const visited = [] 173 | const queue = [start] 174 | const adjacencyList = this.adjacencyList 175 | 176 | visited[start] = true; 177 | while (queue.length) { 178 | let vertex = queue.shift() 179 | results.push(vertex) 180 | 181 | adjacencyList[vertex].forEach((neighbour) => { 182 | if (!visited[neighbour]) { 183 | visited[neighbour] = true 184 | queue.push(neighbour) 185 | } 186 | }) 187 | } 188 | 189 | return results; 190 | } 191 | 192 | } 193 | 194 | let g = new Graph() 195 | 196 | // g.addVertex('Dallas') 197 | // g.addVertex('Tokyo') 198 | // g.addVertex('Aspen') 199 | // g.addVertex('Los Angeles') 200 | // g.addVertex('Hong Kong') 201 | // g.addEdge('Dallas', 'Tokyo') 202 | // g.addEdge('Dallas', 'Aspen') 203 | // g.addEdge('Hong Kong', 'Tokyo') 204 | // g.addEdge('Hong Kong', 'Dallas') 205 | // g.addEdge('Los Angeles', 'Hong Kong') 206 | // g.addEdge('Los Angeles', 'Aspen') 207 | // 208 | // console.log(g.adjacencyList) 209 | // g.removeVertex('Hong Kong') 210 | // console.log(g.adjacencyList) 211 | 212 | g.addVertex('A') 213 | g.addVertex('B') 214 | g.addVertex('C') 215 | g.addVertex('D') 216 | g.addVertex('E') 217 | g.addVertex('F') 218 | 219 | g.addEdge('A', 'B') 220 | g.addEdge('A', 'C') 221 | g.addEdge('B', 'D') 222 | g.addEdge('C', 'E') 223 | g.addEdge('D', 'E') 224 | g.addEdge('D', 'F') 225 | g.addEdge('E', 'F') 226 | 227 | // A 228 | // / \ 229 | // B C 230 | // | | 231 | // D --- E 232 | // \ / 233 | // F 234 | 235 | console.log(g.adjacencyList) 236 | console.log(g.depthFirstRecursive('A')) // [ 'A', 'B', 'D', 'E', 'C', 'F' ] 237 | console.log(g.depthFirstIterative('A')) // [ 'A', 'C', 'E', 'F', 'D', 'B' ] 238 | console.log(g.breadthFirstIterative('A')) // [ 'A', 'B', 'C', 'D', 'E', 'F' ] 239 | -------------------------------------------------------------------------------- /20.DijkstrasAlgorithm.js: -------------------------------------------------------------------------------- 1 | // Dijkstra's Algorithm (Shortest Path Algorithm) 2 | 3 | // One of the most famous and widely used algorithms around! 4 | // Finds the shortest path between two vertices on a graph 5 | // "What's the fastest way to get from point A to point B?" 6 | 7 | // The time complexity of Dijkstra’s Algorithm is O(V + E * log(V)), 8 | // And space complexity is O(|V| + |E|) 9 | // where V is the number of nodes, and E is the number of edges in the graph. 10 | 11 | class Node { 12 | constructor (val, priority) { 13 | this.val = val 14 | this.priority = priority 15 | } 16 | } 17 | 18 | // MinBinaryHeap 19 | class PriorityQueue { 20 | 21 | constructor () { 22 | this.values = [] 23 | } 24 | 25 | enqueue (val, priority) { 26 | let newNode = new Node(val, priority) 27 | this.values.push(newNode) 28 | this.bubbleUp() 29 | } 30 | 31 | bubbleUp () { 32 | let idx = this.values.length - 1 33 | const element = this.values[idx] 34 | while (idx > 0) { 35 | let parentIdx = Math.floor((idx - 1) / 2) 36 | let parent = this.values[parentIdx] 37 | if (element.priority >= parent.priority) break 38 | this.values[parentIdx] = element 39 | this.values[idx] = parent 40 | idx = parentIdx 41 | } 42 | } 43 | 44 | dequeue () { 45 | const min = this.values[0] 46 | const end = this.values.pop() 47 | if (this.values.length > 0) { 48 | this.values[0] = end 49 | this.sinkDown() 50 | } 51 | return min 52 | } 53 | 54 | sinkDown () { 55 | let idx = 0 56 | const length = this.values.length 57 | const element = this.values[0] 58 | while (true) { 59 | let leftChildIdx = 2 * idx + 1 60 | let rightChildIdx = 2 * idx + 2 61 | let leftChild, rightChild 62 | let swap = null 63 | 64 | if (leftChildIdx < length) { 65 | leftChild = this.values[leftChildIdx] 66 | if (leftChild.priority < element.priority) { 67 | swap = leftChildIdx 68 | } 69 | } 70 | if (rightChildIdx < length) { 71 | rightChild = this.values[rightChildIdx] 72 | if ( 73 | (swap === null && rightChild.priority < element.priority) || 74 | (swap !== null && rightChild.priority < leftChild.priority) 75 | ) { 76 | swap = rightChildIdx 77 | } 78 | } 79 | if (swap === null) break 80 | this.values[idx] = this.values[swap] 81 | this.values[swap] = element 82 | idx = swap 83 | } 84 | } 85 | 86 | } 87 | 88 | class WeightedGraph { 89 | 90 | constructor () { 91 | this.adjacencyList = {} 92 | } 93 | 94 | // ADJACENCY LIST STRUCTURE 95 | // { 96 | // A: [ { node: 'B', weight: 4 }, { node: 'C', weight: 2 } ], 97 | // B: [ { node: 'A', weight: 4 }, { node: 'E', weight: 3 } ], 98 | // C: [ 99 | // { node: 'A', weight: 2 }, 100 | // { node: 'D', weight: 2 }, 101 | // { node: 'F', weight: 4 } 102 | // ], 103 | // D: [ 104 | // { node: 'C', weight: 2 }, 105 | // { node: 'E', weight: 3 }, 106 | // { node: 'F', weight: 1 } 107 | // ], 108 | // E: [ 109 | // { node: 'B', weight: 3 }, 110 | // { node: 'D', weight: 3 }, 111 | // { node: 'F', weight: 1 } 112 | // ], 113 | // F: [ 114 | // { node: 'C', weight: 4 }, 115 | // { node: 'D', weight: 1 }, 116 | // { node: 'E', weight: 1 } 117 | // ] 118 | // } 119 | 120 | addVertex (vertex) { 121 | if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = [] 122 | } 123 | 124 | addEdge (vertex1, vertex2, weight) { 125 | if (this.adjacencyList[vertex1] && this.adjacencyList[vertex2]) { 126 | this.adjacencyList[vertex1].push({node: vertex2, weight}) 127 | this.adjacencyList[vertex2].push({node: vertex1, weight}) 128 | } 129 | } 130 | 131 | dijkstra (start, finish) { 132 | const distances = {} // stores the current shortest distace of each vertex from the start point; 133 | const queue = new PriorityQueue() // priority queue 134 | const history = {} 135 | const visited = [] // nodes/vertices we have visited 136 | const path = [] // to return at end 137 | 138 | // build up initial state 139 | for (let vertex in this.adjacencyList) { 140 | if (vertex === start) { 141 | distances[vertex] = 0 142 | queue.enqueue(vertex, 0) 143 | } else { 144 | distances[vertex] = Infinity 145 | queue.enqueue(vertex, Infinity) 146 | } 147 | 148 | history[vertex] = null 149 | } 150 | 151 | let smallest 152 | 153 | // as long as there is something in the priority queue (to visit) 154 | while (queue.values.length) { 155 | smallest = queue.dequeue().val // current smallest value(vertex with least distance) from the start point 156 | 157 | if (smallest === finish) { 158 | // WE ARE DONE 159 | // Build the shortest path to return 160 | while (history[smallest]) { 161 | path.push(smallest) 162 | smallest = history[smallest] 163 | } 164 | break 165 | } 166 | 167 | // for safety 168 | if (smallest || distances[smallest] !== Infinity) { 169 | 170 | for (let neighbour of this.adjacencyList[smallest]) { 171 | // calculate new distance to neighboring node from start point 172 | let candidate = distances[smallest] + neighbour.weight 173 | 174 | // is this distance less than what we are currently storing 175 | if (candidate < distances[neighbour.node]) { 176 | // updating new smallest distance to neighbour from start point 177 | distances[neighbour.node] = candidate 178 | 179 | // update history - How we got to neighbour 180 | history[neighbour.node] = smallest 181 | 182 | // enqueue in priority queue with new priority (current/new smallest distance to reach 183 | // this neighbouring node from the start point 184 | queue.enqueue(neighbour.node, candidate) 185 | } 186 | 187 | } 188 | 189 | } 190 | } 191 | 192 | // shortest path from start point to finish point 193 | return path.concat(smallest).reverse() 194 | } 195 | 196 | } 197 | 198 | const graph = new WeightedGraph() 199 | graph.addVertex('A') 200 | graph.addVertex('B') 201 | graph.addVertex('C') 202 | graph.addVertex('D') 203 | graph.addVertex('E') 204 | graph.addVertex('F') 205 | 206 | graph.addEdge('A', 'B', 4) 207 | graph.addEdge('A', 'C', 2) 208 | graph.addEdge('B', 'E', 3) 209 | graph.addEdge('C', 'D', 2) 210 | graph.addEdge('C', 'F', 4) 211 | graph.addEdge('D', 'E', 3) 212 | graph.addEdge('D', 'F', 1) 213 | graph.addEdge('E', 'F', 1) 214 | 215 | const shortestPath = graph.dijkstra('A', 'E') // [ 'A', 'C', 'D', 'F', 'E' ] 216 | console.log(shortestPath) 217 | -------------------------------------------------------------------------------- /21.Bellman-FordAlgorithm.js: -------------------------------------------------------------------------------- 1 | // Bellman Ford Algorithm (Single Source Shortest Path Graph Algorithm) 2 | 3 | // Bellman-Ford works with negative edges and detects negative weight cycle unlike Dijkstra's Algorithm. 4 | // Bellman-Ford algorithm can handle directed and undirected graphs with non-negative weights. 5 | // However, it can only handle directed graphs with negative weights, as long as we don’t have negative cycles. 6 | 7 | // The time complexity of Bellman-Ford Algorithm is O(V * E)) average case, 8 | // and O(E) best case, O(V^3) worst case. And space complexity is O(|V| + |E|) 9 | // where V is the number of nodes, and E is the number of edges in the graph. 10 | 11 | 12 | // Weighted Undirected Graph 13 | class WeightedGraph { 14 | 15 | constructor () { 16 | this.adjacencyList = {} 17 | } 18 | 19 | // ADJACENCY LIST STRUCTURE 20 | // { 21 | // A: [ { node: 'B', weight: 4 }, { node: 'C', weight: 2 } ], 22 | // B: [ { node: 'A', weight: 4 }, { node: 'E', weight: 3 } ], 23 | // C: [ 24 | // { node: 'A', weight: 2 }, 25 | // { node: 'D', weight: 2 }, 26 | // { node: 'F', weight: 4 } 27 | // ], 28 | // D: [ 29 | // { node: 'C', weight: 2 }, 30 | // { node: 'E', weight: 3 }, 31 | // { node: 'F', weight: 1 } 32 | // ], 33 | // E: [ 34 | // { node: 'B', weight: 3 }, 35 | // { node: 'D', weight: 3 }, 36 | // { node: 'F', weight: 1 } 37 | // ], 38 | // F: [ 39 | // { node: 'C', weight: 4 }, 40 | // { node: 'D', weight: 1 }, 41 | // { node: 'E', weight: 1 } 42 | // ] 43 | // } 44 | 45 | addVertex(vertex) { 46 | if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = [] 47 | } 48 | 49 | addEdge(vertex1, vertex2, weight) { 50 | if (this.adjacencyList[vertex1] && this.adjacencyList[vertex2]) { 51 | this.adjacencyList[vertex1].push({node: vertex2, weight}) 52 | this.adjacencyList[vertex2].push({node: vertex1, weight}) 53 | } 54 | } 55 | 56 | bellmanFord(start, finish) { 57 | const v = Object.keys(this.adjacencyList).length; // number of vertices in the graph 58 | const distances = {} // stores the current shortest distace of each vertex from the start point; 59 | 60 | const history = {} 61 | 62 | const path = [] // to return at end 63 | 64 | // build up initial state 65 | for (let vertex in this.adjacencyList) { 66 | if (vertex === start) { 67 | distances[vertex] = 0 68 | } else { 69 | distances[vertex] = Infinity 70 | } 71 | 72 | history[vertex] = null 73 | } 74 | 75 | 76 | for(let i = 0; i < v - 1; i++) { 77 | 78 | for(let vertex in this.adjacencyList) { 79 | 80 | let source = vertex; 81 | for (let targetNode of this.adjacencyList[vertex]) { 82 | let target = targetNode.node; 83 | let weight = targetNode.weight; 84 | 85 | if(distances[source] + weight < distances[target]) { 86 | distances[target] = distances[source] + weight; 87 | history[target] = source 88 | } 89 | } 90 | 91 | } 92 | 93 | } 94 | 95 | // Iterate one more time. If we still get lesser distance it means 96 | // there is negative weight cycle in the graph. Throw exception in that case 97 | for(let vertex in this.adjacencyList) { 98 | let source = vertex; 99 | for (let j = 0; j < this.adjacencyList[vertex].length; j++) { 100 | let node = this.adjacencyList[vertex][j] 101 | let target = node.node; 102 | let weight = node.weight; 103 | 104 | if(distances[source] + weight < distances[target]) { 105 | throw new Error("Negative Cycle Detected in the graph!") 106 | } 107 | } 108 | } 109 | 110 | // Build the shortest path to return 111 | while (history[finish]) { 112 | path.push(finish) 113 | finish = history[finish] 114 | } 115 | 116 | 117 | // shortest path from start point to finish point 118 | return path.concat(start).reverse() 119 | } 120 | 121 | } 122 | 123 | const graph = new WeightedGraph() 124 | graph.addVertex('A') 125 | graph.addVertex('B') 126 | graph.addVertex('C') 127 | graph.addVertex('D') 128 | graph.addVertex('E') 129 | graph.addVertex('F') 130 | 131 | graph.addEdge('A', 'B', 4) 132 | graph.addEdge('A', 'C', 2) 133 | graph.addEdge('B', 'E', 3) 134 | graph.addEdge('C', 'D', 2) 135 | graph.addEdge('C', 'F', 4) 136 | graph.addEdge('D', 'E', 3) 137 | graph.addEdge('D', 'F', 1) 138 | graph.addEdge('E', 'F', 1) 139 | 140 | const shortestPath = graph.bellmanFord('A', 'E') // [ 'A', 'C', 'D', 'F', 'E' ] 141 | console.log(shortestPath) 142 | -------------------------------------------------------------------------------- /22.TopologicalSort.js: -------------------------------------------------------------------------------- 1 | // Topological Sort is ordering of vertices of a directed acyclic graph such that 2 | // for every edge u, v going from vertex u to vertx v, u should always appear before v in the ordering. 3 | // Topological Sorting for a graph is not possible if the graph is not a DAG. 4 | 5 | // Directed Acyclic Graph (DAG) 6 | 7 | // 0, 1, 2, 3, 4, 5 8 | let adjacencyList = [ [ 1 ], [ 2 ], [], [ 0, 4 ], [], [ 2, 3, 4 ] ]; 9 | 10 | 11 | // v -> number of vertices 12 | // graph -> adjacencyList 13 | function topologicalSort(v, graph) { 14 | const inDegree = new Array(v).fill(0); 15 | 16 | for (let edge of graph) { 17 | for (let target of edge) { 18 | inDegree[target]++; 19 | } 20 | } 21 | 22 | let stack = []; 23 | let order = []; 24 | 25 | for(let i = 0; i < inDegree.length; i++) { 26 | if(inDegree[i] === 0) { 27 | stack.push(i); 28 | } 29 | }; 30 | 31 | while(stack.length) { 32 | const current = stack.pop(); 33 | order.push(current) 34 | 35 | const adjacent = graph[current]; 36 | 37 | for(let i = 0; i < adjacent.length; i++) { 38 | const next = adjacent[i]; 39 | inDegree[next]--; 40 | if(inDegree[next] === 0) { 41 | stack.push(next); 42 | } 43 | } 44 | } 45 | 46 | return order; 47 | 48 | } 49 | 50 | console.log(topologicalSort(6, adjacencyList)) // [ 5, 3, 4, 0, 1, 2 ] 51 | 52 | // recursive implementation of topologicalSort; 53 | 54 | let adjList = { 55 | "A": ["B"], 56 | "B": ["C"], 57 | "C": [], 58 | "D": ["A", "E"], 59 | "E": [], 60 | "F": ["C", "D", "E"] 61 | } 62 | 63 | 64 | // Time Complexity: O(V+E). 65 | // The below algorithm is simply DFS with an extra stack. So time complexity is the same as DFS which is. 66 | // Auxiliary space: O(V). 67 | // The extra space is needed for the stack. 68 | 69 | function topologicalSortRecursive(graph) { 70 | let order = []; 71 | let visited = []; 72 | 73 | for (let vertex in graph) { 74 | if(visited.includes(vertex)) { 75 | continue; 76 | } 77 | 78 | topSort(vertex, order, visited, graph); 79 | } 80 | 81 | return order; 82 | } 83 | 84 | function topSort(vertex, order, visited, adjacencyList) { 85 | visited.push(vertex); 86 | 87 | for(let neighbour of adjacencyList[vertex]) { 88 | if(visited.includes(neighbour)) { 89 | continue; 90 | } 91 | 92 | topSort(neighbour, order, visited, adjacencyList) 93 | } 94 | 95 | 96 | order.unshift(vertex) 97 | } 98 | 99 | 100 | console.log(topologicalSortRecursive(adjList)) // [ 'F', 'D', 'E', 'A', 'B', 'C' ] 101 | -------------------------------------------------------------------------------- /23.2dArray.js: -------------------------------------------------------------------------------- 1 | // 2D Arrays or Matrices 2 | 3 | const testMatrix = [ 4 | [1, 2, 3, 4, 5], 5 | [6, 7, 8, 9, 10], 6 | [11, 12, 13, 14, 15], 7 | [16, 17, 18, 19, 20] 8 | ] 9 | 10 | 11 | // first value is the modification to row, second value is the modification to column 12 | const directions = [ 13 | [-1, 0], //up 14 | [0, 1], //right 15 | [1, 0], //down 16 | [0, -1] //left 17 | ] 18 | 19 | 20 | function traverseDF(matrix) { 21 | const visited = new Array(matrix.length).fill(0).map(() => { 22 | return new Array(matrix[0].length).fill(false) 23 | }); 24 | 25 | const result = []; 26 | 27 | dfs(matrix, 0, 0, visited, result); 28 | 29 | return result; 30 | } 31 | 32 | 33 | function dfs(matrix, row, col, visited, result) { 34 | if(row < 0 || col < 0 || row >= matrix.length || col >= matrix[0].length || visited[row][col]) { 35 | return 36 | } 37 | 38 | visited[row][col] = true; 39 | result.push(matrix[row][col]); 40 | 41 | for(let i = 0; i < directions.length; i++) { 42 | const currentDir = directions[i]; 43 | dfs(matrix, row + currentDir[0], col + currentDir[1], visited, result); 44 | } 45 | } 46 | 47 | 48 | const dfsResult = traverseDF(testMatrix); 49 | console.log(dfsResult); // [ 1, 2, 3, 4, 5, 10, 15, 20, 19, 14, 9, 8, 13, 18, 17, 12, 7, 6, 11, 16] 50 | 51 | 52 | function traverseBF(matrix) { 53 | 54 | const visited = new Array(matrix.length).fill(0).map(() => { 55 | return new Array(matrix[0].length).fill(false) 56 | }); 57 | 58 | const result = []; 59 | const queue = [[0, 0]]; 60 | 61 | while(queue.length) { 62 | const currentPos = queue.shift(); 63 | const row = currentPos[0]; 64 | const col = currentPos[1]; 65 | 66 | if(row < 0 || row >= matrix.length || col < 0 || col >= matrix[0].length || visited[row][col]) { 67 | continue; // skip this iteration of while loop 68 | } 69 | 70 | visited[row][col] = true; 71 | result.push(matrix[row][col]); 72 | 73 | for(let i = 0; i < directions.length; i++) { 74 | const currentDir = directions[i]; 75 | queue.push([row + currentDir[0], col + currentDir[1]]); 76 | } 77 | 78 | } 79 | 80 | return result; 81 | 82 | } 83 | 84 | const bfsResult = traverseBF(testMatrix); 85 | console.log(bfsResult); // [1, 2, 6, 3, 7, 11, 4, 8, 12, 16, 5, 9, 13, 17, 10, 14, 18, 15, 19, 20] 86 | -------------------------------------------------------------------------------- /24.Trie.js: -------------------------------------------------------------------------------- 1 | // Tries or Prefix Trees 2 | 3 | class TrieNode { 4 | constructor() { 5 | this.children = {}; 6 | this.endOfWord = false; 7 | } 8 | } 9 | 10 | class Trie { 11 | constructor() { 12 | this.root = new TrieNode(); 13 | } 14 | 15 | // Iterative implementation of insert into trie 16 | // Time complexity: O(length_of_word) 17 | insert(word) { 18 | let current = this.root; 19 | 20 | for(let i = 0; i < word.length; i++) { 21 | let ch = word[i]; 22 | let node = current.children[ch]; 23 | 24 | if(!node) { 25 | node = new TrieNode(); 26 | current.children[ch] = node; 27 | }; 28 | 29 | current = node; 30 | } 31 | 32 | // mark the current node's endOfWord as true 33 | current.endOfWord = true; 34 | }; 35 | 36 | // Recursive implementation of insert into trie 37 | insertRecursive(word, node = this.root, index=0) { 38 | 39 | if(index === word.length) { 40 | node.endOfWord = true; 41 | return 42 | } 43 | 44 | let ch = word[index]; 45 | let child = node.children[ch]; 46 | if(!child) { 47 | child = new TrieNode(); 48 | node.children[ch] = child 49 | } 50 | 51 | this.insertRecursive(word, child, index+1); 52 | } 53 | 54 | 55 | 56 | // Iterative implementation of search in a trie. 57 | // Time complexity: O(length_of_word) 58 | search(word) { 59 | let current = this.root; 60 | 61 | for(let i = 0; i < word.length; i++) { 62 | let ch = word[i]; 63 | let node = current.children[ch]; 64 | 65 | // if node does not exist for given char then return false; 66 | if(!node) { 67 | return false; 68 | } 69 | 70 | current = node; 71 | } 72 | 73 | // return true if current's endOfWord is true else return false. 74 | return current.endOfWord; 75 | }; 76 | 77 | 78 | // Recursive implementation of search into trie. 79 | searchRecursive(word, currentNode = this.root, index = 0) { 80 | if(word.length === index && currentNode.endOfWord) { 81 | return true 82 | } 83 | 84 | if (word.length === 0 && !currentNode.endOfWord) { 85 | return false 86 | } 87 | 88 | let ch = word[index]; 89 | let child = currentNode.children[ch]; 90 | 91 | // if node/child doesn't exist for given character, then return false 92 | if(!child) { 93 | return false 94 | } 95 | 96 | return this.searchRecursive(word, child, index+1) 97 | } 98 | 99 | // AUTO-COMPLETE (PREFIX SEARCH) 100 | startsWith(prefix) { 101 | let current = this.root; 102 | 103 | // Traverse till last character in `prefix` 104 | for(let i = 0; i < prefix.length; i++) { 105 | let ch = prefix[i]; 106 | let node = current.children[ch]; 107 | 108 | if(!node) { 109 | // return false 110 | return [] 111 | } 112 | 113 | current = node; 114 | }; 115 | 116 | const possibleWords = this.prefixSearch(prefix, current); 117 | return possibleWords; 118 | // return true; 119 | } 120 | 121 | 122 | prefixSearch(prefix, current, possibleWords=[]) { 123 | for(let childNode of Object.entries(current.children)) { 124 | let ch = childNode[0]; 125 | let node = childNode[1]; 126 | prefix = prefix + ch; 127 | 128 | if(node.endOfWord) { 129 | // console.log(prefix) 130 | possibleWords.push(prefix); 131 | } 132 | 133 | this.prefixSearch(prefix, node, possibleWords); 134 | prefix = prefix.slice(0, prefix.length-1) // Backtracking 135 | } 136 | 137 | return possibleWords; 138 | } 139 | 140 | } 141 | 142 | 143 | 144 | const trie = new Trie(); 145 | 146 | trie.insertRecursive("apple"); 147 | trie.insertRecursive("apply"); 148 | trie.insertRecursive("application") 149 | console.log(trie.search("apple")); // returns true 150 | console.log(trie.searchRecursive("app")); // returns false 151 | console.log(trie.startsWith("app")); // returns true, [ 'apple', 'apply', 'application' ] 152 | trie.insert("dog") 153 | trie.insert("app"); 154 | console.log(trie.search("app")); // returns true 155 | -------------------------------------------------------------------------------- /25.DynamicProgramming.js: -------------------------------------------------------------------------------- 1 | // WHAT IS DYNAMIC PROGRAMMING 2 | 3 | // "A method for solving a complex problem by breaking it down into a collection 4 | // of simpler subproblems, solving each of those subproblems just once, and storing their solutions." 5 | 6 | // "Using past knowledge to make solving a future problem easier" 7 | 8 | // IT ONLY WORKS ON PROBLEMS WITH... 9 | // OPTIMAL SUBSTRUCTURE & 10 | // OVERLAPPING SUBPROBLEMS 11 | 12 | // OVERLAPPING SUBPROBLEMS 13 | // A problem is said to have overlapping subproblems if it can be broken down into subproblems which are reused several times 14 | // e.g, Fibonacci Sequence 15 | 16 | // OPTIMAL SUBSTRUCTURE 17 | // A problem is said to have optimal substructure if an optimal solution can be constructed from optimal solutions of its subproblems 18 | 19 | 20 | // recursive - O( 2^n ), more precisely O( 1.6^n ), exponential time complexity 21 | // space complexity is O(n) 22 | function fib(n){ 23 | if(n <= 2) return 1; 24 | return fib(n-1) + fib(n-2); 25 | } 26 | 27 | // WHAT IS THE SUBSTRUCTURE? 28 | // fib(n) = fib(n - 1) + fib(n - 2); 29 | 30 | 31 | // Two Flavours of Dynamic Programming 32 | 33 | // 1. MEMOIZATION - TOP-DOWN APPROACH 34 | // Storing the results of expensive function calls and returning the cached result when the same inputs occur again 35 | 36 | 37 | // memoized - O(n), linear time complexity 38 | function fib_memo(n, memo=[]){ 39 | if(memo[n] !== undefined) return memo[n] 40 | if(n <= 2) return 1; 41 | let res = fib(n-1, memo) + fib(n-2, memo); 42 | memo[n] = res; 43 | return res; 44 | } 45 | 46 | // function fib_memo(n, memo=[undefined, 1, 1]){ 47 | // if(memo[n] !== undefined) return memo[n]; 48 | // var res = fib(n-1, memo) + fib(n-2, memo); 49 | // memo[n] = res; 50 | // return res; 51 | // } 52 | 53 | 54 | // function fib_memo(n, savedFib={}) { 55 | // // base case 56 | // if (n <= 0) { return 0; } 57 | // if (n <= 2) { return 1; } 58 | // 59 | // // memoize 60 | // if (savedFib[n - 1] === undefined) { 61 | // savedFib[n - 1] = fib(n - 1, savedFib); 62 | // } 63 | // 64 | // // memoize 65 | // if (savedFib[n - 2] === undefined) { 66 | // savedFib[n - 2] = fib(n - 2, savedFib); 67 | // } 68 | // 69 | // return savedFib[n - 1] + savedFib[n - 2]; 70 | // } 71 | 72 | 73 | // Say you are a traveller on a 2D grid. You start at the top left corner and your goal 74 | // is to travel to the bottom right corner. You can only move down or right. 75 | // In how many ways can can you travel to the goal on a grid of width m * n; 76 | // O(2^ n+m) time, O(n+m) space 77 | function gridTraveler(m, n) { 78 | if(m === 1 && n === 1) return 1; 79 | if(m === 0 || n === 0) return 0; 80 | 81 | return gridTraveler(m-1, n) + gridTraveler(m, n-1) 82 | // m-1 represents going down, n-1 represents going right 83 | } 84 | 85 | function gridTravelerMemo(m, n, memo={}) { 86 | let key = `${m},${n}`; 87 | 88 | if(key in memo) return memo[key]; 89 | if(m === 1 && n === 1) return 1; 90 | if(m === 0 || n === 0) return 0; 91 | 92 | memo[key] = gridTraveler(m-1, n, memo) + gridTraveler(m, n-1, memo); 93 | return memo[key] 94 | } 95 | 96 | gridTraveler(3, 3) // 6 97 | 98 | 99 | function canConstruct(target, wordBank, memo={}) { 100 | if(target in memo) return memo[target]; 101 | if(target === "") return true; 102 | 103 | for (let word of wordBank) { 104 | if(target.indexOf(word) === 0) { 105 | let suffix = target.slice(word.length); 106 | if(canConstruct(suffix, wordBank, memo) === true) { 107 | memo[targrt] = true; 108 | return true 109 | } 110 | } 111 | } 112 | 113 | memo[target] = false 114 | return memo[target]; 115 | } 116 | 117 | canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"]) // true; 118 | 119 | 120 | // 2. TABULATION - BOTTOM-UP APPROACH 121 | // Storing the result of a previous result in a "table" (usually an array) 122 | // Usually done using iteration 123 | // Better space complexity can be achieved using tabulation 124 | 125 | 126 | // tabulated 127 | function fib_table(n){ 128 | if(n <= 2) return 1; 129 | var fibNums = [0,1,1]; 130 | for(var i = 3; i <= n; i++){ 131 | fibNums[i] = fibNums[i-1] + fibNums[i-2]; 132 | } 133 | return fibNums[n]; 134 | } 135 | 136 | 137 | // function fib_table(n){ 138 | // const table = Array(n+1).fill(0) 139 | // table[1] = 1 140 | // for(var i = 0; i <= n; i++){ 141 | // table[i + 1] += table[i]; 142 | // table[i + 2] += table[i]; 143 | // } 144 | // return table[n]; 145 | // } 146 | 147 | 148 | // function fib_table(n){ 149 | // const lastTwo = [0, 1]; 150 | // let counter = 3; 151 | // 152 | // while (counter <= n) { 153 | // const nextFib = last[0] + last[1]; 154 | // 155 | // lastTwo[0] = lastTwo[1]; 156 | // lastTwo[1] = nextFib; 157 | // 158 | // counter++ 159 | // } 160 | // 161 | // return n > 1 ? lastTwo[1] : lastTwo[0]; 162 | // } 163 | 164 | // let calculations = 0; 165 | // function fibonacci(n) { //O(2^n) 166 | 167 | // if (n < 2) { 168 | // return n 169 | // } 170 | // return fibonacci(n-1) + fibonacci(n-2); 171 | // } 172 | 173 | // function fibonacciMaster() { //O(n) 174 | // let cache = {}; 175 | // return function fib(n) { 176 | // calculations++; 177 | // if (n in cache) { 178 | // return cache[n]; 179 | // } else { 180 | // if (n < 2) { 181 | // return n; 182 | // } else { 183 | // cache[n] = fib(n-1) + fib(n-2); 184 | // return cache[n]; 185 | // } 186 | // } 187 | // } 188 | // } 189 | 190 | // function fibonacciMaster2(n) { 191 | // let answer = [0,1]; 192 | // for ( let i = 2; i <= n; i++) { 193 | // answer.push(answer[i-2]+ answer[i-1]); 194 | // } 195 | // return answer.pop(); 196 | // } 197 | 198 | // const fasterFib = fibonacciMaster(); 199 | 200 | // console.log('Slow', fibonacci(35)) 201 | // console.log('DP', fasterFib(100)); 202 | // console.log('DP2', fibonacciMaster2(100)); 203 | // console.log('we did ' + calculations + ' calculations'); 204 | 205 | 206 | 207 | // LCS (Longest Common subsequence) 208 | 209 | // MEMOIZATION, TOP DOWN APPROACH 210 | let longestCommonSubsequence = function(text1, text2) { 211 | 212 | function dp(i, j, memo={}) { 213 | 214 | if(`${i},${j}` in memo) { 215 | return memo[`${i},${j}`] 216 | } 217 | 218 | if(i === -1 || j === -1) { 219 | return 0; 220 | } 221 | 222 | if(text1[i] === text2[j]) { 223 | memo[`${i},${j}`] = 1 + dp(i-1, j-1, memo); 224 | return memo[`${i},${j}`] 225 | } else { 226 | memo[`${i},${j}`] = Math.max(dp(i-1, j, memo), dp(i, j-1, memo)) 227 | return memo[`${i},${j}`] 228 | } 229 | 230 | 231 | } 232 | 233 | return dp(text1.length-1, text2.length-1) 234 | }; 235 | 236 | 237 | // TABULATION, BOTTOM UP APPROACH 238 | let longestCommonSubsequenceTabulated = function(text1, text2) { 239 | 240 | text1 = " " + text1; 241 | text2 = " " + text2; 242 | 243 | let width = text1.length; 244 | let height = text2.length; 245 | 246 | let table = [...Array(height)].map(x => [...Array(width)].map(ele => 0)) 247 | 248 | for(let y = 1; y < height; y++) { 249 | for(let x = 1; x < width; x++) { 250 | 251 | if(text1[x] === text2[y]) { 252 | // with the same character 253 | // extend the length of common subsequence 254 | table[y][x] = 1 + table[y-1][x-1] 255 | } else { 256 | // with different characters 257 | // choose the optimal subsequence 258 | table[y][x] = Math.max(table[y-1][x], table[y][x-1]) 259 | } 260 | } 261 | } 262 | 263 | return table[height-1][width-1] 264 | }; 265 | 266 | longestCommonSubsequence("abcde", "ace") // 3 267 | -------------------------------------------------------------------------------- /26.heapSort.js: -------------------------------------------------------------------------------- 1 | 2 | // O(nlogn) time complexity 3 | function heapSort(arr) { 4 | var n = arr.length; 5 | 6 | // Build max heap (rearrange array) 7 | for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { 8 | heapify(arr, n, i); 9 | } 10 | 11 | // One by one extract an element from heap 12 | for (let i = n - 1; i > 0; i--) { 13 | // Move current root to end 14 | let temp = arr[0]; 15 | arr[0] = arr[i]; 16 | arr[i] = temp; 17 | 18 | // call max heapify on the reduced heap 19 | heapify(arr, i, 0); 20 | } 21 | } 22 | 23 | 24 | // O(logn) time comolexity 25 | function heapify(arr, n, i) 26 | { 27 | var largest = i; // Initialize largest as root 28 | var l = 2 * i + 1; // left = 2*i + 1 29 | var r = 2 * i + 2; // right = 2*i + 2 30 | 31 | // If left child is larger than root 32 | if (l < n && arr[l] > arr[largest]) 33 | largest = l; 34 | 35 | // If right child is larger than largest so far 36 | if (r < n && arr[r] > arr[largest]) 37 | largest = r; 38 | 39 | // If largest is not root 40 | if (largest != i) { 41 | var swap = arr[i]; 42 | arr[i] = arr[largest]; 43 | arr[largest] = swap; 44 | 45 | // Recursively heapify the affected sub-tree 46 | heapify(arr, n, largest); 47 | } 48 | } 49 | 50 | 51 | let arr = [ 5, 12, 11, 13, 4, 6, 7 ]; 52 | let n = arr.length; 53 | 54 | heapSort(arr); 55 | console.log(arr); // [4, 5, 6, 7, 11, 12, 13] 56 | -------------------------------------------------------------------------------- /27.LRUCache.js: -------------------------------------------------------------------------------- 1 | // LRU (Least Recently Used) Cache 2 | 3 | class Node { 4 | constructor(key, value) { 5 | this.key = key 6 | this.value = value 7 | this.next = null 8 | this.prev = null 9 | } 10 | } 11 | 12 | 13 | class DoublyLinkedList { 14 | constructor() { 15 | this.head = null 16 | this.tail = null 17 | this.length = 0 18 | } 19 | 20 | 21 | push(key, value) { 22 | const newNode = new Node(key, value); 23 | 24 | // if list is empty 25 | if(!this.head) { 26 | this.head = this.tail = newNode 27 | this.length++ 28 | return this 29 | } 30 | 31 | const currentTail = this.tail; 32 | currentTail.next = newNode; 33 | newNode.prev = currentTail 34 | this.tail = newNode 35 | this.length++ 36 | return this; 37 | } 38 | 39 | 40 | pop() { 41 | 42 | // if there is no element in the list 43 | if(!this.head) { 44 | return null 45 | } 46 | 47 | // if there is only one element in the list 48 | if(!this.head.next) { 49 | const currentTail = this.tail; 50 | this.head = this.tail = null; 51 | this.length-- 52 | return currentTail 53 | } 54 | 55 | const last = this.tail; 56 | const secondLast = last.prev; 57 | secondLast.next = null; 58 | last.prev = null 59 | this.tail = secondLast; 60 | this.length-- 61 | return last; 62 | } 63 | 64 | 65 | shift() { 66 | // if there is no element in the list 67 | if(!this.head) { 68 | return null 69 | } 70 | // if there is only one element in the list 71 | if(!this.head.next) { 72 | const currentHead = this.head 73 | this.head = this.tail = null 74 | this.length-- 75 | return currentHead 76 | } 77 | 78 | const currentHead = this.head; 79 | const newHead = currentHead.next; 80 | currentHead.next = null; 81 | newHead.prev = null; 82 | this.head = newHead; 83 | this.length-- 84 | return currentHead 85 | } 86 | 87 | unshift(key, value) { 88 | const newNode = new Node(key, value); 89 | const currentHead = this.head; 90 | 91 | // if list was empty 92 | if(!currentHead) { 93 | this.head = this.tail = newNode; 94 | this.length++ 95 | return this; 96 | } 97 | 98 | newNode.next = currentHead 99 | currentHead.prev = newNode 100 | this.head = newNode 101 | this.length++ 102 | return this; 103 | } 104 | 105 | setHead(node) { 106 | if(this.head === node) { 107 | return; 108 | } else if (this.head === null) { 109 | this.head = node; 110 | this.tail = node; 111 | } else if (this.head === this.tail) { 112 | this.tail.prev = node; 113 | this.head = node; 114 | this.head.next = this.tail; 115 | } else { 116 | if(this.tail === node) { 117 | this.pop(); 118 | } 119 | 120 | if(node.prev) { 121 | node.prev.next = node.next; 122 | } 123 | 124 | if(node.next) { 125 | node.next.prev = node.prev; 126 | } 127 | 128 | node.next = null; 129 | node.prev = null; 130 | 131 | this.head.prev = node; 132 | node.next = this.head; 133 | this.head = node; 134 | } 135 | } 136 | 137 | } 138 | 139 | 140 | class LRUCache { 141 | 142 | constructor(maxSize = 1) { 143 | this.maxSize = maxSize; 144 | this.cache = {}; 145 | this.currentSize = 0; 146 | this.list = new DoublyLinkedList(); 147 | } 148 | 149 | 150 | // O(1) time | O(1) space; 151 | set(key, value) { 152 | 153 | if(key in this.cache) { 154 | this.replaceKey(key, value); 155 | this.updateMostRecent(this.cache[key]) 156 | return; 157 | } 158 | 159 | if(this.currentSize === this.maxSize) { 160 | this.evictLeastRecent(); 161 | } else { 162 | this.currentSize++ 163 | } 164 | 165 | this.cache[key] = new Node(key, value); 166 | this.updateMostRecent(this.cache[key]) 167 | 168 | } 169 | 170 | 171 | // O(1) time | O(1) space; 172 | get(key) { 173 | if(key in this.cache) { 174 | this.updateMostRecent(this.cache[key]) 175 | return this.cache[key].value; 176 | } 177 | 178 | return null; 179 | } 180 | 181 | 182 | // O(1) time | O(1) space; 183 | getMostRecent(key) { 184 | return this.list.head.key; 185 | } 186 | 187 | // O(1) time | O(1) space; 188 | getLeastRecent(key) { 189 | return this.list.tail.key; 190 | } 191 | 192 | 193 | replaceKey(key, value) { 194 | this.cache[key].value = value; 195 | } 196 | 197 | evictLeastRecent() { 198 | let deleted = this.list.pop(); // removes tail(least recently used); 199 | delete this.cache[deleted.key]; 200 | } 201 | 202 | updateMostRecent(node) { 203 | this.list.setHead(node); 204 | } 205 | } 206 | 207 | 208 | let cache = new LRUCache(4); 209 | 210 | cache.set("a", 1); 211 | cache.set("b", 2); 212 | cache.set("c", 3); 213 | cache.set("d", 4); 214 | console.log(cache.getLeastRecent()) // a 215 | console.log(cache.get("d")) // 4 216 | cache.set("a", 10); 217 | console.log(cache.get("a")) // 10 218 | console.log(cache.getMostRecent()) // a 219 | -------------------------------------------------------------------------------- /28.FloydWarshallAlgorithm.js: -------------------------------------------------------------------------------- 1 | // The Floyd Warshall Algorithm is for solving the "All Pairs Shortest Path problem". 2 | // The problem is to find shortest distances between every pair of vertices in a given edge weighted directed Graph. 3 | 4 | // Input: 5 | // graph = [ [0, 5, INF, 10], 6 | // [INF, 0, 3, INF], 7 | // [INF, INF, 0, 1}, 8 | // [INF, INF, INF, 0] ] 9 | // which represents the following graph 10 | // 10 11 | // (0)------->(3) 12 | // | /|\ 13 | // 5 | | 14 | // | | 1 15 | // \|/ | 16 | // (1)------->(2) 17 | // 3 18 | // Note that the value of graph[i][j] is 0 if i is equal to j 19 | // And graph[i][j] is INF (infinite) if there is no edge from vertex i to j. 20 | 21 | 22 | // Time Complexity: O(V^3) 23 | function floydWarshall(graph) { 24 | let vertices = graph.length; // number of vertices 25 | 26 | // let dist = Array.from(Array(graph.length), () => new Array(graph.length).fill(0)) 27 | let dist = [...Array(vertices)].map(x => [...Array(vertices)].map(ele => 0)) 28 | let i, j, k 29 | 30 | // Initialize the solution matrix same as input graph matrix 31 | // Or we can say the initial values of shortest distances 32 | // are based on shortest paths considering no intermediate vertex 33 | for (i = 0; i < vertices; i++) { 34 | for (j = 0; j < vertices; j++) { 35 | dist[i][j] = graph[i][j] 36 | } 37 | } 38 | 39 | for (k = 0; k < vertices; k++) { 40 | 41 | // Pick all vertices as source one by one 42 | for (i = 0; i < vertices; i++) { 43 | 44 | // Pick all vertices as destination for the above picked source 45 | for (j = 0; j < vertices; j++) { 46 | 47 | // If vertex k is on the shortest path from i to j, then update the value of dist[i][j] 48 | if (dist[i][k] + dist[k][j] < dist[i][j]) { 49 | dist[i][j] = dist[i][k] + dist[k][j] 50 | } 51 | } 52 | } 53 | } 54 | 55 | // Detecting negative cycle using Floyd Warshall 56 | // If distance of any vertex from itself becomes negative, then there is a negative weight cycle. 57 | for (i = 0; i < vertices; i++) { 58 | if (dist[i][i] < 0) { 59 | console.log("Negative Cycle Detected") 60 | } 61 | } 62 | 63 | console.log("No negative Cycle Detected in the Graph") 64 | 65 | return dist; 66 | } 67 | 68 | 69 | let graph = [ 70 | [0, 5, Infinity, 10], 71 | [Infinity, 0, 3, Infinity], 72 | [Infinity, Infinity, 0, 1], 73 | [Infinity, Infinity, Infinity, 0], 74 | ]; 75 | 76 | 77 | 78 | 79 | console.log(floydWarshall(graph)); 80 | 81 | // Result: 82 | 83 | // [ 84 | // [ 0, 5, 8, 9 ], 85 | // [ Infinity, 0, 3, 4 ], 86 | // [ Infinity, Infinity, 0, 1 ], 87 | // [ Infinity, Infinity, Infinity, 0 ] 88 | // ] 89 | -------------------------------------------------------------------------------- /29.PrimsAlgorithm.js: -------------------------------------------------------------------------------- 1 | // Prims’s Minimum Spanning Tree Algorithm , Minimum Cost Spanning Tree(MST) | Greedy Algorithm 2 | 3 | // What is Minimum Spanning Tree? 4 | // Given a connected and undirected graph, a spanning tree of that graph is a sub graph that is a tree and connects all the vertices together. 5 | // A single graph can have many different spanning trees. A minimum spanning tree (MST) or minimum weight spanning tree for a weighted, 6 | // connected, undirected graph is a spanning tree with a weight less than or equal to the weight of every other spanning tree. 7 | // The weight of a spanning tree is the sum of weights given to each edge of the spanning tree. 8 | // How many edges does a minimum spanning tree has? 9 | // A minimum spanning tree has (V – 1) edges where V is the number of vertices in the given graph. 10 | 11 | 12 | 13 | 14 | class PriorityQueue { 15 | 16 | constructor(){ 17 | this.values = []; 18 | } 19 | 20 | enqueue(from, to, weight) { 21 | this.values.push({from, to, weight}); 22 | this.sort(); 23 | }; 24 | 25 | dequeue() { 26 | return this.values.shift(); 27 | }; 28 | 29 | sort() { 30 | this.values.sort((a, b) => a.weight - b.weight); 31 | }; 32 | 33 | } 34 | 35 | 36 | // for Adjacency Matrix 37 | function primsAlgorithm(graph) { 38 | let vertices = graph.length // number of vertices or nodes in the graph; 39 | let m = vertices - 1 // number of edges in spanning tree 40 | 41 | let edgeCount = 0; 42 | let mstCost = 0; 43 | 44 | let mstEdges = [] // length m 45 | 46 | // visited[i] tracks whether node/vertex i has beeen visited or not; 47 | const visited = [...Array(vertices)].map(ele => false); 48 | const queue = new PriorityQueue(); 49 | 50 | // initialize the queue with edges from node 0 to rest of nodes 51 | for(let i = 1; i < vertices; i++) { 52 | if(graph[0][i] !== Infinity) { 53 | queue.enqueue(0, i, graph[0][i]) 54 | } 55 | } 56 | 57 | visited[0] = true; 58 | 59 | // Loop while the MST is not complete 60 | while(edgeCount !== m && queue.values.length > 0) { 61 | 62 | let minEdge = queue.dequeue(); 63 | 64 | if(!visited[minEdge.to]) { 65 | for(let i = 0; i < vertices; i++) { 66 | 67 | // enque all the neighbours(not visited) of minEdge 68 | if(!visited[i] && graph[minEdge.to][i] != Infinity) { 69 | queue.enqueue(minEdge.to, i, graph[minEdge.to][i]) 70 | } 71 | } 72 | 73 | mstCost += minEdge.weight; 74 | visited[minEdge.to] = true; 75 | edgeCount++ 76 | mstEdges.push(minEdge); 77 | } 78 | } 79 | 80 | // Make sure MST spans the whole graph 81 | if (edgeCount !== m) { 82 | return null 83 | }; 84 | 85 | return mstEdges; 86 | } 87 | 88 | 89 | /* Let us create the following graph 90 | 2 3 91 | (0)--(1)--(2) 92 | | / \ | 93 | 6| 8/ \5 |7 94 | | / \ | 95 | (3)-------(4) 96 | 9 */ 97 | 98 | 99 | let graph = [ 100 | // 0 1 2 3 4 101 | [ Infinity, 2, Infinity, 6, Infinity ], // 0 102 | [ 2, Infinity, 3, 8, 5 ], // 1 103 | [ Infinity, 3, Infinity, Infinity, 7 ], // 2 104 | [ 6, 8, Infinity, Infinity, 9 ], // 3 105 | [ Infinity, 5, 7, 9, Infinity ] // 4 106 | ]; 107 | 108 | console.log(primsAlgorithm(graph)) 109 | 110 | //Result: 111 | 112 | // Min Cost = 16 113 | 114 | // [ 115 | // { from: 0, to: 1, weight: 2 }, 116 | // { from: 1, to: 2, weight: 3 }, 117 | // { from: 1, to: 4, weight: 5 }, 118 | // { from: 0, to: 3, weight: 6 } 119 | // ] 120 | 121 | 122 | 123 | // For Adjacency List 124 | function primsAlgorithm2(graph) { 125 | let vertices = graph.length // number of vertices or nodes in the graph; 126 | let m = vertices - 1 // number of edges in spanning tree 127 | 128 | let edgeCount = 0; 129 | let mstCost = 0; 130 | 131 | let mstEdges = [] // length m 132 | 133 | // visited[i] tracks whether node/vertex i has beeen visited or not; 134 | const visited = [...Array(vertices)].map(ele => false); 135 | const queue = new PriorityQueue(); 136 | 137 | function addEdges(nodeIndex) { 138 | 139 | // mark the current node as visited 140 | visited[nodeIndex] = true; 141 | 142 | // iterate over all the edges going outwards from the current node 143 | // add edges to the queue which point to unvisited nodes 144 | 145 | let edges = graph[nodeIndex]; 146 | 147 | for(let edge of edges) { 148 | if(!visited[edge.to]) { 149 | queue.enqueue(nodeIndex, edge.to, edge.weight); // {from: nodeIndex, to: edge.to, weight: edge.weight} 150 | } 151 | } 152 | } 153 | 154 | addEdges(0); 155 | 156 | while(edgeCount !== m && queue.values.length > 0) { 157 | let minEdge = queue.dequeue(); 158 | 159 | let nextNode = minEdge.to; 160 | 161 | if(visited[nextNode]) { 162 | continue 163 | } 164 | 165 | mstCost += minEdge.weight; 166 | edgeCount++ 167 | mstEdges.push(minEdge); 168 | 169 | addEdges(nextNode) 170 | 171 | } 172 | 173 | // No MST exists 174 | if (edgeCount !== m) { 175 | return null 176 | }; 177 | 178 | return mstEdges; 179 | 180 | } 181 | 182 | class node { 183 | constructor (to, weight) 184 | { 185 | this.to = to; 186 | this.weight = weight; 187 | } 188 | } 189 | 190 | 191 | class Graph { 192 | constructor() { 193 | this.adjacencyList = []; 194 | } 195 | 196 | 197 | addEdge(src ,dest, weight) { 198 | let node1 = new node(dest, weight); 199 | let node2 = new node(src, weight); 200 | 201 | if(!this.adjacencyList[src]) { 202 | this.adjacencyList[src] = [] 203 | } 204 | 205 | if(!this.adjacencyList[dest]) { 206 | this.adjacencyList[dest] = [] 207 | } 208 | 209 | this.adjacencyList[src].push(node1); 210 | this.adjacencyList[dest].push(node2); 211 | } 212 | } 213 | 214 | 215 | let adjacencyList = new Graph(); 216 | adjacencyList.addEdge(0, 1, 4); 217 | adjacencyList.addEdge(0, 7, 8); 218 | adjacencyList.addEdge(1, 2, 8); 219 | adjacencyList.addEdge(1, 7, 11); 220 | adjacencyList.addEdge(2, 3, 7); 221 | adjacencyList.addEdge(2, 8, 2); 222 | adjacencyList.addEdge(2, 5, 4); 223 | adjacencyList.addEdge(3, 4, 9); 224 | adjacencyList.addEdge(3, 5, 14); 225 | adjacencyList.addEdge(4, 5, 10); 226 | adjacencyList.addEdge(5, 6, 2); 227 | adjacencyList.addEdge(6, 7, 1); 228 | adjacencyList.addEdge(6, 8, 6); 229 | adjacencyList.addEdge(7, 8, 7); 230 | 231 | console.log(primsAlgorithm2(adjacencyList.adjacencyList)) 232 | 233 | // Result: 234 | 235 | // Min Cost = 37 236 | 237 | // [ 238 | // { from: 0, to: 1, weight: 4 }, 239 | // { from: 0, to: 7, weight: 8 }, 240 | // { from: 7, to: 6, weight: 1 }, 241 | // { from: 6, to: 5, weight: 2 }, 242 | // { from: 5, to: 2, weight: 4 }, 243 | // { from: 2, to: 8, weight: 2 }, 244 | // { from: 2, to: 3, weight: 7 }, 245 | // { from: 3, to: 4, weight: 9 } 246 | // ] 247 | -------------------------------------------------------------------------------- /30.DisJointSet.js: -------------------------------------------------------------------------------- 1 | // Applications of DisJoint Sets: 2 | 3 | // Kruskal’s Minimum Spanning Tree Algorithm. 4 | // Job Sequencing Problem. 5 | // Cycle Detection 6 | 7 | // 1. ARRAY Representation: 8 | 9 | // Data Structures used: 10 | // 1. Parent Array : An array of integers, called parent[]. If we are dealing with n items, i’th element of the array 11 | // represents the i’th item. More precisely, the i’th element of the array is the parent of the i’th item. 12 | // These relationships create one, or more, virtual trees 13 | // 2. Rank Array : An array of integers called rank[]. Size of this array is same as the parent array. 14 | // If i is a representative of a set, rank[i] is the height of the tree representing the set. 15 | 16 | class DisJointSet { 17 | 18 | constructor(n) { 19 | this.rank = Array(n).fill(1); 20 | this.parent = [...Array(n)].map((x, idx) => idx); 21 | } 22 | 23 | 24 | // Finds Set(parent or representative) of given item x 25 | find(x) { 26 | 27 | if(this.parent[x] === x) { 28 | return this.parent[x]; 29 | } 30 | 31 | //Finds the representative of the set that x is an element of 32 | 33 | // we recursively call Find on its parent 34 | let result = this.find(this.parent[x]); 35 | 36 | // path compression 37 | // move i's node directly under the representative of this Set 38 | this.parent[x] = result; 39 | 40 | return this.parent[x]; 41 | } 42 | 43 | 44 | // Do union of two sets represented by x and y. 45 | union(x, y) { 46 | 47 | const xSet = this.find(x); 48 | const ySet = this.find(y); 49 | 50 | // both x and y belong to same set 51 | if(xSet === ySet) { 52 | return 53 | } 54 | 55 | 56 | // Union by Rank 57 | // Put smaller ranked item under bigger ranked item if ranks are different 58 | if(this.rank[xSet] < this.rank[ySet]) { 59 | this.parent[xSet] = ySet 60 | } else if(this.rank[ySet] < xSet) { 61 | this.parent[ySet] = xSet 62 | } else { 63 | // If ranks are same, then move y under x (doesn't matter which one goes where) and increment rank of x's tree; 64 | 65 | this.parent[ySet] = xSet; 66 | this.rank[xSet] = this.rank[xSet] + 1; 67 | } 68 | } 69 | } 70 | 71 | 72 | 73 | set = new DisJointSet(5) 74 | set.union(0, 2) 75 | set.union(4, 2) 76 | set.union(3, 1) 77 | 78 | if (set.find(4) === set.find(0)) { 79 | console.log('Belongs to same set') 80 | } else { 81 | console.log("Belongs to different sets") 82 | } 83 | 84 | if (set.find(1) === set.find(0)) { 85 | console.log('Belongs to same set') 86 | } else { 87 | console.log("Belongs to different sets") 88 | } 89 | 90 | 91 | // 2. Tree Representation 92 | 93 | class Node { 94 | constructor(data, rank) { 95 | this.data = data 96 | this.parent = this; 97 | this.rank = rank 98 | } 99 | } 100 | 101 | class DisJointSet2 { 102 | 103 | constructor() { 104 | this.map = {}; 105 | } 106 | 107 | makeSet(data) { 108 | let node = new Node(data, 0); 109 | this.map[data] = node; 110 | } 111 | 112 | 113 | 114 | find(data) { 115 | let node = this.map[data]; 116 | 117 | if(node.parent.data === data) { 118 | return node.parent 119 | } 120 | 121 | node.parent = this.find(node.parent.data); 122 | 123 | return node.parent 124 | } 125 | 126 | union(x, y) { 127 | let xNode = this.map[x]; 128 | let yNode = this.map[y]; 129 | 130 | let xSet = this.find(xNode.data); 131 | let ySet = this.find(yNode.data); 132 | 133 | // if they are part of same set do nothing 134 | if(xSet.data === ySet.data) { 135 | return; 136 | } 137 | 138 | // else whoever's rank is higher becomes parent of other 139 | if(xSet.rank < ySet.rank) { 140 | xSet.parent = ySet; 141 | } else if(ySet.rank < xSet.rank) { 142 | ySet.parent = xSet; 143 | } else { 144 | ySet.parent = xSet; 145 | xSet.rank = xSet.rank + 1 146 | } 147 | } 148 | 149 | } 150 | 151 | 152 | ds = new DisJointSet2(); 153 | ds.makeSet(1); 154 | ds.makeSet(2); 155 | ds.makeSet(3); 156 | ds.makeSet(4); 157 | ds.makeSet(5); 158 | ds.makeSet(6); 159 | ds.makeSet(7); 160 | 161 | ds.union(1, 2); 162 | ds.union(2, 3); 163 | ds.union(4, 5); 164 | ds.union(6, 7); 165 | ds.union(5, 6); 166 | ds.union(3, 7); 167 | 168 | console.log(ds.find(1)); // node with data 4 169 | console.log(ds.find(2)); // node with data 4 170 | console.log(ds.find(3)); // node with data 4 171 | console.log(ds.find(4)); // node with data 4 172 | console.log(ds.find(5)); // node with data 4 173 | console.log(ds.find(6)); // node with data 4 174 | console.log(ds.find(7)); // node with data 4 175 | -------------------------------------------------------------------------------- /31.Kruskal'sAlgorithm.js: -------------------------------------------------------------------------------- 1 | // Kruskal’s Minimum Spanning Tree Algorithm | Greedy Algorithm 2 | 3 | 4 | // The steps for finding MST using Kruskal’s algorithm 5 | 6 | // 1. Sort all the edges in non-decreasing order of their weight. 7 | // 2. Pick the smallest edge. Check if it forms a cycle with the spanning tree formed so far (if the vertices of the edge belong to same set) 8 | // If cycle is not formed, include this edge. Else, discard it. 9 | // 3. Repeat step#2 until there are (V-1) edges in the spanning tree. 10 | 11 | // Time Complexity: O(ElogE) or O(ElogV). Sorting of edges takes O(ELogE) time. 12 | 13 | class Graph { 14 | 15 | constructor(vertices) { 16 | this.vertices = vertices 17 | this.graph = [] 18 | this.parent = [...Array(vertices)].map((x, idx) => idx); 19 | this.rank = Array(vertices).fill(1); 20 | } 21 | 22 | 23 | addEdge(u, v, w) { 24 | this.graph.push([u, v, w]) 25 | } 26 | 27 | // Finds Set(parent or representative) of given item x 28 | find(x) { 29 | 30 | if(this.parent[x] === x) { 31 | return this.parent[x]; 32 | } 33 | 34 | //Finds the representative of the set that x is an element of 35 | 36 | // we recursively call Find on its parent 37 | let result = this.find(this.parent[x]); 38 | 39 | // path compression 40 | // move i's node directly under the representative of this Set 41 | this.parent[x] = result; 42 | 43 | return this.parent[x]; 44 | } 45 | 46 | // Do union of two sets represented by x and y. 47 | union(x, y) { 48 | 49 | const xSet = this.find(x); 50 | const ySet = this.find(y); 51 | 52 | // both x and y belong to same set 53 | if(xSet === ySet) { 54 | return 55 | } 56 | 57 | 58 | // Union by Rank 59 | // Put smaller ranked item under bigger ranked item if ranks are different 60 | if(this.rank[xSet] < this.rank[ySet]) { 61 | this.parent[xSet] = ySet 62 | } else if(this.rank[ySet] < xSet) { 63 | this.parent[ySet] = xSet 64 | } else { 65 | // If ranks are same, then move y under x (doesn't matter which one goes where) and increment rank of x's tree; 66 | 67 | this.parent[ySet] = xSet; 68 | this.rank[xSet] = this.rank[xSet] + 1; 69 | } 70 | } 71 | } 72 | 73 | 74 | 75 | function kruskalsAlgorithm(graph) { 76 | let vertices = graph.vertices // number of vertices or nodes in the graph; 77 | let m = vertices - 1 // number of edges in spanning tree 78 | 79 | let mstEdges = [] // length m 80 | let edgeCount = 0; 81 | let mstCost = 0; 82 | 83 | // sort the graph in ascending order of edge weight 84 | 85 | graph.graph.sort((a, b) => a[2] - b[2]); 86 | 87 | 88 | // Create V(no. of vertices) subsets with single elements(vertex); 89 | let parent = graph.parent; 90 | let rank = graph.rank 91 | 92 | 93 | let i = 0; 94 | // Number of edges to be taken is equal to V-1 95 | while (edgeCount < m) { 96 | 97 | // Pick the smallest edge and increment the index for next iteration; 98 | let minEdge = graph.graph[i]; 99 | i = i + 1 100 | let [u, v, w] = minEdge; 101 | 102 | let x = graph.find(u) 103 | let y = graph.find(v) 104 | 105 | // If including this edge doesn't cause cycle(i.e, x !== y), include it in result 106 | if(x !== y) { 107 | mstEdges.push(minEdge); 108 | mstCost += w 109 | graph.union(x, y) 110 | edgeCount += 1 111 | } // else discard the edge 112 | 113 | } 114 | 115 | console.log(mstCost) 116 | return mstEdges; 117 | } 118 | 119 | 120 | 121 | g = new Graph(4) 122 | g.addEdge(0, 1, 10) 123 | g.addEdge(0, 2, 6) 124 | g.addEdge(0, 3, 5) 125 | g.addEdge(1, 3, 15) 126 | g.addEdge(2, 3, 4) 127 | 128 | console.log(kruskalsAlgorithm(g)) 129 | 130 | // Result: 131 | 132 | // Minimum Cost of Spanning Tree: 19 133 | 134 | // [ 135 | // [ 2, 3, 4 ], 136 | // [ 0, 3, 5 ], 137 | // [ 0, 1, 10 ] 138 | // ] 139 | -------------------------------------------------------------------------------- /32.Kosaraju'sAlgorithm.js: -------------------------------------------------------------------------------- 1 | // Strongly Connected Components 2 | 3 | // A directed graph is strongly connected if there is a path between all pairs of vertices. 4 | // A strongly connected component (SCC) of a directed graph is a maximal strongly connected subgraph. 5 | 6 | // We can find all strongly connected components in O(V+E) time using Kosaraju’s Algorithm. 7 | 8 | // Time Complexity: The Kosaraju's algorithm calls DFS, finds reverse of the graph and again calls DFS. 9 | // DFS takes O(V+E) for a graph represented using adjacency list. 10 | // Reversing a graph also takes O(V+E) time. For reversing the graph, we simple traverse all adjacency lists. 11 | 12 | 13 | class Graph { 14 | 15 | constructor(vertices) { 16 | this.vertices = vertices; 17 | this.adjacencyList = [...Array(vertices)].map((x) => []); 18 | } 19 | 20 | addEdge(src, dest) { 21 | 22 | if (!this.adjacencyList[src].includes(dest)) { 23 | this.adjacencyList[src].push(dest); 24 | } 25 | } 26 | 27 | dfsUtilForReversedGraph(vertex, visited, set) { 28 | // Mark the current node as visited; 29 | visited[vertex] = true; 30 | set.push(vertex); 31 | 32 | for(let neighbour of this.adjacencyList[vertex]) { 33 | // Recur for all the vertices(not visited yet) adjacent to this vertex 34 | if(!visited[neighbour]) { 35 | this.dfsUtilForReversedGraph(neighbour, visited, set) 36 | } 37 | } 38 | } 39 | 40 | 41 | 42 | // fillStack: Fill vertices in stack according to their finishing times 43 | dfsUtil(vertex, visited, stack) { 44 | // Mark the current node as visited 45 | visited[vertex]= true 46 | 47 | // Recur for all the vertices adjacent to this vertex 48 | for(let neighbour of this.adjacencyList[vertex]) { 49 | if(!visited[neighbour]) { 50 | this.dfsUtil(neighbour, visited, stack) 51 | } 52 | } 53 | 54 | stack.push(vertex); 55 | } 56 | 57 | 58 | // Function that returns reverse (or transpose) of this graph 59 | getTranspose() { 60 | let g = new Graph(this.vertices); 61 | 62 | for(let vertex = 0; vertex < this.vertices; vertex++) { 63 | 64 | // Recur for all the vertices adjacent to this vertex 65 | for(let neighbour of this.adjacencyList[vertex]) { 66 | g.addEdge(neighbour, vertex) 67 | } 68 | } 69 | 70 | return g; 71 | } 72 | 73 | 74 | // The main function that finds all the strongly connected components 75 | findSCCs() { 76 | 77 | let stack = []; 78 | let scc = []; 79 | 80 | // Mark all the vertices as not visited (For first DFS) 81 | let visited = Array(this.vertices).fill(false); 82 | 83 | 84 | // Fill vertices in stack according to their finishing times 85 | for(let vertex = 0; vertex < this.vertices; vertex++) { 86 | if(!visited[vertex]) { 87 | this.dfsUtil(vertex, visited, stack) 88 | } 89 | } 90 | 91 | // Create a reversed graph 92 | let g = this.getTranspose() 93 | 94 | // Mark all the vertices as not visited (For second DFS) 95 | visited = Array(this.vertices).fill(false); 96 | 97 | // Now process all vertices in order defined by Stack 98 | // Do a DFS based off vertex finish time in decreasing order on reverse graph.. 99 | while(stack.length > 0) { 100 | 101 | let vertex = stack.pop(); 102 | 103 | if(visited[vertex]) { 104 | continue; 105 | } 106 | 107 | let set = []; 108 | 109 | g.dfsUtilForReversedGraph(vertex, visited, set); 110 | scc.push(set); 111 | } 112 | 113 | return scc; 114 | } 115 | 116 | } 117 | 118 | 119 | let g = new Graph(5) 120 | g.addEdge(1, 0) 121 | g.addEdge(0, 2) 122 | g.addEdge(2, 1) 123 | g.addEdge(0, 3) 124 | g.addEdge(3, 4) 125 | 126 | console.log("Following are strongly connected components in given graph:") 127 | 128 | console.log(g.findSCCs()) // [ [ 0, 1, 2 ], [ 3 ], [ 4 ] ] 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Saalik Mubeen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data Structures And Algorithms 2 | 3 | ### For JavaScript lovers | JavaScript Implementation of various data structures and algorithms. 4 | 5 | Screw java and C++ . This repository's goal is to demonstrate how to correctly implement common data structures and algorithms in everyone's favorite, JavaScript. 6 | 7 | 8 | # Contributing 9 | 10 | This repository is contribution friendly :smiley:. If you'd like to add or improve an algorithm, your contribution is welcome! 11 | --------------------------------------------------------------------------------