Data Structures and Algorithms
Bitwise OR (|)
returns a number whose binary representation has a 1 in each bit position for which the corresponding bits of either or both operands are 1.
Best Time to Buy And Sell Stock Algorithm
maxprofit = 0, min = infinity if current val < min, min = current val else if current val - min > maxprofit, maxprofit = current val - min
Root of Number Algo (y^n == x. Given x and n, find y)
/* Binary search over the potential range (0, max(1, x)) if mid^n == x (+- .001) return mid */ if (x == 0) return 0; let low = 0; let high = Math.max(1, x); while (high > low) { let mid = (low + high) / 2 if (Math.pow(mid,n) <= x + .001 && Math.pow(mid,n) >= x - .001) { return mid } if (Math.pow(mid, n) > x) high = mid; else low = mid; } } // Time: O(log(x)) x being the input (y^n == x) // Space: O(1)
Search a 2D Matrix (Time & Space)
/* Binary search to find row. If target less than row[0], top = mid -1 If target more than row[-1], bottom = mid + 1 Binary search found row for target. If row or target not found, return false * */ const binarySearch = (row) => { // binary search matrix[row] } let cols = matrix[0].length let top = matrix.length - 1; let bot = 0; while (top >= bot) { let row = Math.floor((top + bot) / 2); if (target >= matrix[row][0] && target <= matrix[row][cols - 1]){ return binarySearch(row) } if (target < matrix[row][0]) { top = row - 1 } else { bot = row + 1 } } return false Time: O(log(m)) + O(log(n)) Space: O(1)
BST In Order Successor Search Algorithm (Time & Space)
/* If inputNode has a right child (greater than it), sucessor will be the furthest left value (min) in the tree of its right child. Otherwise, successor will be an ancestor of inputNode. Specifically, it will be the first ancestor node of which the child is its left child. */ const dfsAncestor = () => { let ancestor = inputNode.parent; let child = inputNode; while (ancestor !== null && child == ancestor.right) { child = ancestor ancestor = child.parent } return ancestor } const dfsChild = (node) => { while (node.left !== null) { node = node.left } return node; } return inputNode.right != null ? dfsChild(inputNode.right) : dfsAncestor(); } Time: O(H) --height of bst this is: log(n) for balanced tree, O(n) for unbalanced Space: O(1)
Island Count Algorithm (Time & Space)
/* Init count variable, visited map, stack Create helper func to check if coords in range Iterate through If cell is a 0, or already visited, continue Otherwise (it's a 1, and not visited) Increment count, add cell to stack Work through the stack If out of range, visited, or 0: continue Otherwise: Add to visited, push all neighbors to stack */ let count = 0; let visited = new Map(); let stack = []; const isInRange = (x, y) => { return x < 0 || x > binaryMatrix.length - 1 || y < 0 || y > binaryMatrix[0].length - 1 } const markIsland = (i, j) => { count ++; stack.push([i, j]); while (stack.length > 0) { let [x, y] = stack.pop(); if (isInRange(x,y)|| visited.has(x+''+y)){ continue;} if (binaryMatrix[x][y] === 0) {continue;} visited.set(x+''+y, 1); stack.push([(x +1), y], [(x -1) ,y ], [x ,(y + 1)], [x ,(y - 1)]); } } for (let i = 0; i < binaryMatrix.length; i ++) { for (let j = 0; j < binaryMatrix[i].length; j ++) { if (binaryMatrix[i][j] == 0 || visited.has(i + '' + j)) { continue; } else { markIsland(i,j) } } } return count } Time: O(N * M) Space: O(N * M)
Find Smallest Non-Negative not in Array Algo
/* Iterate through If an integer is not located at index == its value I.e arr[3] == 3 And its value is within range Swap it into its correct place And repeat until arr[i] == i iterate through again and return first arr[i] !== i otherwise, array is consecutive ints from 0, return array.length */ let n = arr.length; let temp = 0; for (let i = 0; i < n; i++){ temp = arr[i]; while (temp < n && arr[temp] !== temp) { [arr[i], arr[temp]] = [arr[temp], arr[i]]; temp = arr[i]; } } for (let i = 0; i < n; i++) { if (arr[i] !== i) { return i; } } return n; Space: O(1); Time: O(N);
Validate IP Algo (Time & Space)
/* Split IP by periods. If resulting array != 4, false Validate each segment with helper function Create helper function to validate segments If segment length 0, false For each character If char not within UTF-16 range for single digit, false (i.e charCodeAt must be 48-57) */ const validateChunk = (chunk) => { if (chunk.length == 0) return false; for (let i = 0; i < chunk.length; i++) { const charCode = chunk.charCodeAt(i); if (charCode < 48 || charCode > 57) return false; } return 0 <= Number(chunk) && Number(chunk) <= 255; } const chunks = ip.split('.'); if (chunks.length != 4) return false; for (let segment of chunks) { if (!validateChunk(segment)) return false } return true; Time: O(N) Space: O(N)
Bracket Match Algorithm (Time & Space)
/* if opening, add to opening if closing and opening > 0 decrement opening if closing and opening < 1 increment closing return closing + opening */ let closing = 0; let opening = 0; for (let i = 0; i < text.length; i ++) { if (text[i] === '(') { opening ++ } else if (text[i] === ')' && opening > 0 ) { opening -- } else { closing ++ } } return closing + opening Space: O(1) Time: O(N)
Pairs with Specific Difference Algorithm (Time & Space)
/* for each element, calc and store: element - k : element (the key/ y value needed to satisfy equation): (value/ x) then, for each element: (these are y's), see if it is in the map (is a y paired with an x). If so, push to output value from the map, and the current number (our key) (x, y) ~Storing by x, fetching by y maintains order of y element in original array~ */ if (k == 0) return [] const output = new Array(); const map = new Map(); for (let i = 0; i < arr.length; i++) { map.set(arr[i] - k, arr[i]); } for (let i = 0; i < arr.length; i++) { if(map.has(arr[i])){ output.push([map.get(arr[i]), arr[i]]) } } return output; Time: O(N) Space: O(N)
Reverse Linked List (iterative algorithm)
/* Iterative/ 2 pointer approach */ let prev = null; let current = head; let temp; while (current !== null) { temp = current.next current.next = prev; prev = current current = temp } return prev;
Car Fleet Algo (Time & Space)
/** Create an array containing each car as [position, speed] Sort array in descending order (closest to target first) Iterate through array Calculate time to target for each car: (target - position)/ speed Push the time to target onto stack If there are atleast two items on stack See if item 2 (stack[-2]) is greater/equal than item 1 I.e will the current car catch up with the car ahead of it If so, pop (cars form fleet at speed of lead car) Return stack length (only lead cars remain on stack) Time: NLog(N) Space: N */ const pairs = []; const stack = []; for (let i = 0; i < position.length; i++) { pairs.push([position[i], speed[i]]); } pairs.sort((a, b) => { return b[0] - a[0]; }); for (let i = 0; i < pairs.length; i++) { let carPosition = pairs[i][0]; let carSpeed = pairs[i][1]; let timeToTarget = (target - carPosition) / carSpeed; stack.push(timeToTarget); if ( stack.length > 1 && stack[stack.length - 1] <= stack[stack.length - 2] ) { stack.pop(); } } return stack.length;
Word Ladder Algo (Space & Time)
/** Create neighbors graph (all potential adjacent word patterns) I.e for 'pig': '*ig': [pig]; 'p*g': [pig], etc. Do so for each word (word list and starting word) Create a visited nodes set, a queue, and a 'level' count BFS through graph loop over queue.length (a 'level / step' in the graph) If target, return Else, iterate over the word, examining each pattern (i.e pig: *ig, p*g, pi*) For each word (neighbor) in that pattern Add to the queue if not visited Having completed a step/ level, increment level count */ if (!wordList.includes(endWord)) return 0; const neighbors = {}; wordList.push(beginWord) for (let word of wordList) { for (let i = 0; i < word.length; i++) { let pattern = word.substring(0, i) + '*' + word.substring(i + 1); if (!neighbors[pattern]) neighbors[pattern] = []; neighbors[pattern].push(word); } } wordList.pop(); let visited = new Set(); let queue = [beginWord]; let level = 1; while (queue.length > 0) { let size = queue.length; for (let k = 0; k < size; k++) { let currentWord = queue.shift(); if (currentWord === endWord) return level; for (let i = 0; i < currentWord.length; i++) { let pattern = currentWord.substring(0, i) + '*' + currentWord.substring(i + 1); if (neighbors[pattern]) { for (let neighbor of neighbors[pattern]) { if (!visited.has(neighbor)) { visited.add(neighbor); queue.push(neighbor); } }} }} level++; } return 0; Time & Space: O(N * M^2) (M is word length)
Combination Sum Backtracking Algo
/** DFS(index, current, total) Recursive case/call: At each step, option to include or exclude a number (push/pop) If excluded, proceed to the next number Base cases: Do so until index exceeds candidates.length, or Until the total exceeds the target, or Until total == target (add current to result) Time O(N * ((Target/MIN) + 1)) | Space O(N * (Target/Min)) */ const res = []; const dfs = (i, cur, total) => { if (i >= candidates.length || total > target) { return } else if (total == target) { res.push(cur.slice()) return } cur.push(candidates[i]); dfs(i, cur, total + candidates[i]); cur.pop(); dfs(i + 1, cur, total) } dfs(0, [], 0); return res;
Count Good Nodes in BST Algo
/** DFS. Pass maxPathVal with each call, increment good nodes if current node >= max Time: O(N) Space: O(max height) Use BFS to switch space to max width */ let goodNodes = 0; const dfs = (maxPathVal = 0, node) => { if (!node) return; if (node.val >= maxPathVal){ goodNodes += 1; maxPathVal = node.val } dfs(maxPathVal, node.left); dfs(maxPathVal, node.right) } dfs(root.val, root); return goodNodes;
Find Min in Rotated, Sorted Arr Algo
/** If array not rotated, return left Else, binary search until window is the right hand array (smaller arr) then return left */ if (nums.length == 1) return nums[0]; let left = 0; let right = nums.length - 1; while (left < right) { if (nums[left] < nums[right]) return nums[left]; const mid = Math.floor((left + right) / 2); if (nums[mid] >= nums[left]) { left = mid + 1; } else { right = mid; } } return nums[left]; Space: O(1) Time: O(log(n))
Lowest Common Ancestor of BST Algo
/** If both p & q are smaller or larger than current node, That means LCA must be to the other side Otherwise, there is one on each side (or current node is p/q), and current node is LCA. Time O(N) | Space O(1) */ while (root !== null) { if (p.val > root.val && q.val > root.val) { root = root.right; continue } else if (p.val < root.val && q.val < root.val) { root = root.left; continue } else { return root } }
Longest Repeating Character Algo (Time & Space)
/** Inititate: a map, result variable, and a pointer. Iterate over length of string: Get length of window (right ptr - left ptr + 1) Increment letter val or set letter val to 1. If the current window is no longer valid (i.e window length - max val from map is > k): (Advance window. Current window will not work. Shift it along until end of string. Update result if larger window found.) Decrement the value of ltr at left ptr. Increment left ptr. Update value of window length. Set result to max of length, current result. */ let res = 0; let count = new Map(); let l = 0; for (let r = 0; r < s.length; r++) { let len = r - l + 1 count.set(s[r], 1 + (count.get(s[r]) || 0)) if ((len - Math.max(...count.values())) > k) { count.set(s[l], count.get(s[l]) - 1) l++; }; len = r - l + 1; res = Math.max(res, len); }; return res; Time: O(N) Space: O(1)
Binary Tree Right Side View Algo
/** Level order traversal, return final node of each level Time: O(N) Space: O(max-width) */ const output = []; if (!root) return output; const queue = [root]; const bfs = () => { while (queue.length) { let size = queue.length; for (let i = 0; i < size; i ++) { const node = queue.shift(); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); if (i == size - 1) output.push(node.val) } } } bfs(); return output;
Koko Eating Bananas Algo (Time & Space)
/** Piles.length >= h (hours to eat) So, at most, bananas per hour (k) == maximum value in piles. Minimum value of k is 1. Binary search from 1 - max. Test mid as k value. See how many hours it takes to eat all bananas with k/ mid. I.e: piles.forEach(hours += pile / k) If less than h (hours allowed) If less than result Set result to mid/ k Eat slower/ decr k (right = mid - 1) Else: Eat faster/ incr k (left = mid + 1) */ let l = 1; let r = Math.max(...piles); let result = r; while (l <= r) { let mid = Math.floor( (l + r) / 2); let hours = 0; piles.forEach((pile) => { hours += Math.ceil(pile / mid); }) if ( hours <= h ){ result = Math.min(result, mid) r = mid - 1 } else { l = mid + 1; } } return result }; Time: O(N * log(m)) M = max pile size Space: O(1)
BST from Preorder, Inorder Algo
/** Preorder[0] is always root Find index of root in inorder Use that to partition the array/tree into left and right Recursively call function with assignment to root.l, root.r Passing in left chunk of preorder, inorder Right chunk of preorder, inorder Return root */ if (preorder.length === 0 || inorder.length === 0) { return null; } let root = new TreeNode(preorder[0]); let mid = inorder.indexOf(preorder[0]); root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid)); root.right = buildTree(preorder.slice(mid + 1), inorder.slice(mid + 1)); return root; Time: O(N) Space: O(H) if implemented with indices };
All Possible Subsets Algo
/** Time: O(N * 2^N) Space: O(N) For each index in nums, option is to include or exclude When nums.length of these decision have been made (index), The resulting subset should be pushed to the results array */ const res = []; const subset = []; const dfs = (i) => { console.log(i, subset) if (i >= nums.length) { res.push(subset.slice()); return } subset.push(nums[i]); dfs(i + 1); subset.pop(); dfs(i + 1); } dfs(0); return res;
Time Based Key-Value Store Algo
/** To set: store timestamp: value pairs in an array such that this.map[key] = [[timestamp: value]] To get: Init value to '' If key DNE, return value Otherwise binary search array Each time the target is larger than the guess, Store guess as current max value This will return max in case timestamp DNE And if timestamp smaller than all values, value will not be reset and '' will return */ class TimeMap { constructor() { this.map = {}; } set(key, value, timestamp) { const bucket = this.map[key] || []; this.map[key] = bucket; bucket.push([value, timestamp]); } get(key, timestamp) { let value = '' let bucket = this.map[key] || [] let [left, right] = [0, bucket.length - 1]; while (left <= right) { const mid = (left + right) >> 1; const [guessValue, guessTimestamp] = bucket[mid]; if (guessTimestamp <= timestamp) { value = guessValue; left = mid + 1; } if (timestamp < guessTimestamp) right = mid - 1; } return value; } Time: O(1) for set, O(log(n)) for get Space: O(1)
Add Two Numbers (Algorithm) Time and Space
/** Use carry over addition. Remember edge cases: Lists of differing lengths; carryOver remains after lists exhausted. */ let l3 = new ListNode(); let ptr = l3; let carryOver = 0; let sum; while (l1 || l2 || carryOver) { sum = (l1 ? l1.val : 0) + (l2 ? l2.val : 0) + carryOver; carryOver = Math.floor(sum / 10); l3.next = new ListNode(sum % 10) l3 = l3.next if (l1) { l1 = l1.next; } if (l2) { l2 = l2.next; } } return ptr.next Time: O(N) Space: O(N)
Reorder List Algo (12345 --> 15243)
/** Use slow and fast pointers (1 step & 2 steps) to find halfway point Separate into two lists at halfway point create second list by assigning second = slow.next separate by assigning slow.next = null Reverse second list Merge lists While second not null Save list.next in temp variables for each list Assign first.next = second (second head) Assign second.next = temp1 (first.next) Advance pointers first = temp1, second = temp2 */ let slow = head; let fast = head.next; while (fast && fast.next) { slow = slow.next; fast = fast.next.next; } let second = slow.next; slow.next = null; let prev = null; while (second) { let temp = second.next; second.next = prev; prev = second; second = temp; } let first = head; second = prev; while (second) { let temp1 = first.next; let temp2 = second.next; first.next = second; second.next = temp1; first = temp1; second = temp2; } Time: O(N) Space: O(1)
Remove Nth Node From End of List Algo
/** Use two pointers, update one with delay of n steps Advance ptr1 n times If ptr1 is null, return head.next (n == length, so that's just removing the head) While ptr1 is not null Save ptr2 previous Advance ptr2, advance ptr1 Ptr2 while point to the node to remove Prev.next = ptr2.next to remove */ let ptr1 = head; let ptr2 = head; let prev; for (let i = 0; i < n; i++) { ptr1 = ptr1.next } if (!ptr1) { return head.next; } while (ptr1) { prev = ptr2; ptr2 = ptr2.next; ptr1 = ptr1.next } prev.next = ptr2.next; return head; Time: O(L) L being length Space: O(1)
Find the Duplicate Num Algo (Floyd's)
/** Uses Floyd's cycle detection algo. Just memorize it. */ let slow = 0; let fast = 0; while (true) { slow = nums[slow] fast = nums[nums[fast]] if (slow == fast) { break; } } let slow2 = 0; while (true) { slow = nums[slow]; slow2 = nums[slow2]; if (slow == slow2) { return slow } } Time: O(N) Space: O(1)
MinStack Algorithm
/** Create a parallel min stack. With each push to main stack, push current min to min stack (Min(value, minStack[length - 1])) */ var MinStack = function() { this.stack = []; this.minStack = []; }; MinStack.prototype.push = function(val) { this.stack.push(val); if (this.minStack.length > 0) { val = Math.min(this.minStack[this.minStack.length - 1], val) } this.minStack.push(val); }; MinStack.prototype.pop = function() { this.stack.pop() this.minStack.pop(); }; MinStack.prototype.top = function() { return this.stack[this.stack.length - 1]; }; MinStack.prototype.getMin = function() { return this.minStack[this.minStack.length - 1]; };
Prefix Trie Algo
/** Implement a TrieNode with children : {} and end : true/false End indicates if there is a word terminating at that character For all methods, search through one node at a time Time: O(N) Space: O(N) */ class TrieNode { constructor(children = {}, end = false) { this.children = children; this.end = end; } } var Trie = function () { this.root = new TrieNode(); }; Trie.prototype.insert = function (word) { let cur = this.root; for (let i = 0; i < word.length; i++) { const char = word[i]; if (!cur.children[char]) { cur.children[char] = new TrieNode(); } cur = cur.children[char]; } cur.end = true; }; Trie.prototype.search = function (word) { let cur = this.root; for (let i = 0; i < word.length; i++) { const char = word[i]; if (!cur.children[char]) { return false; } cur = cur.children[char]; } return cur.end; }; Trie.prototype.startsWith = function (prefix) { let cur = this.root; for (let i = 0; i < prefix.length; i++) { const char = prefix[i]; if (!cur.children[char]) { return false; } cur = cur.children[char]; } return true; };
LRU (least recently used) Cache Algo
/** * https://leetcode.com/problems/lru-cache/ * Time O(1) | Space O(N) * Your LRUCache object will be instantiated and called as such: * var obj = new LRUCache(capacity) * var param_1 = obj.get(key) * obj.put(key,value) */ class LRUCache { constructor(capacity) { this.capacity = capacity; this.map = new Map(); this.head = {}; this.tail = {}; this.head.next = this.tail; this.tail.prev = this.head; } removeLastUsed () { const [ key, next, prev ] = [ this.head.next.key, this.head.next.next, this.head ]; this.map.delete(key); this.head.next = next; this.head.next.prev = prev; } put (key, value) { const hasKey = this.get(key) !== -1; const isAtCapacity = this.map.size === this.capacity; if (hasKey) return (this.tail.prev.value = value); if (isAtCapacity) this.removeLastUsed(); const node = { key, value }; this.map.set(key, node); this.moveToFront(node); } moveToFront (node) { const [ prev, next ] = [ this.tail.prev, this.tail ]; this.tail.prev.next = node; this.connectNode(node, { prev, next }); this.tail.prev = node; } connectNode (node, top) { node.prev = top.prev; node.next = top.next; } get (key) { const hasKey = this.map.has(key); if (!hasKey) return -1; const node = this.map.get(key); this.disconnectNode(node); this.moveToFront(node); return node.value; } disconnectNode (node) { node.next.prev = node.prev; node.prev.next = node.next; } } Time: O(1) Space: O(N)
Daily Temperatures Algorithm (Time & Space)
/** Create stack, 0 filled array of length of input array Iterate through array While stack not empty and current temp greater than top of stack Pop from stack [day, temp] Get difference between popped day and current i Store difference at day index in result Push current index and temp to stack Works bc stack increases from top down I.e. you only add elements to stack if smaller than top of stack */ const stack = []; const result = new Array(temperatures.length).fill(0) for (let i = 0; i < temperatures.length; i++){ while(stack.length && temperatures[i] > stack[stack.length - 1][1]){ let day = stack.pop()[0]; result[day] = (i - day); } stack.push([i, temperatures[i]]) } return result; Space: O(N) Time: O(N)
Divide and Conquer (steps)
1. Figure out the base case (simplest possible case) 2. Divide or decrease problem until it becomes the base case
JS Array Operations (access/edit, indexOf, push/pop, shift/unshift, splice, array iterations, concat)
Access/edit: 1 indexOf: N Push/pop: 1 Shift/unshift: N Splice: N Iterations (filter, map, reduce, find, etc): N Concat: N + M (array1.length + array2.length)
Bitwise left shift & right shift
<< (left) >> (right). returns a number whose binary representation is the first operand shifted by the specified number of bits to the left/right. Excess bits shifted off to the left/right are discarded, and zero bits are shifted in from the right/left.
Dijkstra's algorithm
Assumes three hash tables as input: Graph, Costs, Parents get_lowest_cost_node = loop through nodes in costs. initialize lowest_cost_node as infinity. Compare each node to lowest_cost_node. If lower and not in processed, set it as lowest_cost and lowest_cost_node node = get_lowest_cost_node(costs) while (node !== False) { cost = costs[node] neighbors = graph[node] for n in neighbors.keys() { new_cost = cost + neighbors[n] if (costs[n] > new_cost) { costs[n] = new_cost parents[n] = node processed.push(node) node = get_lowest_cost_node(costs) Qualifier: Can't use with negative weight edges
Greedy algorithms
At each step, pick the locally optimal solution, and in the end hope for the globally optimal solution. Generally provide a "good enough" solution and are easy to write.
Parts of recursive function and their definitions
Base case: when the function doesn't call itself again Recursive case: when the function calls itself again
KNN (K-Nearest Neighbors)
Classification: categorize user/node into group Regression: predicting a response (like a number)
DFS Algo ( Use Cases)
DFS = (root) => { if (!root) return root.children.forEach((child) => { DFS(child) )} } Use Cases: explores as far as possible along each branch before backtracking, whereas BFS explores all nodes at the current depth before moving to the next level. commonly used for tasks such as topological sorting, cycle detection, and tasks where a solution at depth is preferred.
Queue
First in first out. Add to front, pull from back
Tree
Graph where no edges point back (single direction, hierarchical)
Logarithm
Inverse of an exponential function; base assumed to be 2; to what power to you need to raise the base to equal the n?
Selection sort
Iterate through a list n times, each time selecting the most (insert condition, e.g. large) element, removing it from the list, and adding it to a new, sorted list. Repeat until original list is empty. Time: O(n^2) Space: O(1) Use case: When memory space is limited
Stack
Last in first out. Add to the top, pull from the top
Sets
Like lists, but do not allow duplicates. Sets have operations including union, intersection, and difference.
Merge Sort V Quick Sort
Merge Sort: Stable, guarantees O(n log n) time, good for large datasets and linked lists due to consistent performance. Quicksort: Faster in practice with average-case efficiency, be cautious of potential worst-case O(n^2); often preferred for its smaller constant factors and cache-friendly behavior.
NP-complete problems
No known fast solution. Indicators include: "all combinations/ all possible versions of x", calculating an optimal sequence, or sets. Use an approximation algorithm
Topological Sort
Nodes that are dependent upon other nodes (predecessors) show up after them in a list.
Array v Linked List
Search times: Array: O(1) Linked list: O(n) Insert/Delete: Array: O(n), or O(1) if from end of list Linked list: O(1) from beginning or end (if known), or O(n) if from middle Summary: Linked lists use memory more efficiently. Linked lists have faster insert/deletion. Arrays have faster search time, and are better for accessing random elements.
Hash Table Time Complexity
Search: O(1) Insert: O(1) Delete: O(1)
Binary Search Tree (time)
Search: log n Insert: log n Delete: log n Much faster for insert and delete than arrays
Binary Search
Starts at the middle of a sorted list and removes half of the data; this process repeats until the desired value is found-- then return its position--or all elements have been eliminated-- else return null. Time: O(log n) Space: O(1) Use case: Sorted lists
Call Stack
Used to save a program's function calls and each call's variable values. Items are pushed to the top of the stack when called, popped off the top of the stack when completed.
Dynamic programming
Useful when trying to optimize something given a constraint. Can be used when a problem can be broken down into discrete subproblems. Solutions involve grids. No single formula for calculating dynamic-programming solution.
Prefix Trie (with wildcard search) Algo
class TrieNode { constructor() { this.children = {}; this.isWord = false; } } class WordDictionary { constructor() { this.root = new TrieNode(); } addWord(word, node = this.root) { for (const char of word) { const child = node.children[char] || new TrieNode(); node.children[char] = child; node = child; } node.isWord = true; } search(word) { return this.searchHelper(word, this.root, 0); } searchHelper(word, node, index) { if (!node) return false; if (index == word.length) return node.isWord; if (word[index] === '.') return this.dfs(word, node, index); return this.searchHelper(word, node.children[word[index]], index + 1); } dfs(word, node, index) { for (const char of Object.keys(node.children)) { const child = node.children[char]; const hasWord = this.searchHelper(word, child, index + 1); if (hasWord) return true; } return false; } } /** Search recursively, passing word, current node, current index Base cases: Node does not exist (false); index == word.length (return word.isWord) If character is a wildcard, call dfs. Otherwise, progress to next node, next index For wildcard DFS: call search helper function on each child. If it returns true, return true. Otherwise, do nothing, proceed to next child in loop If no word found, return false Time: O(N) Space: O(N) */
Generate Parentheses Algo (All combos, well-formed)
const combos = []; if (n === 0) { combos.push(''); return combos } for (let c = 0; (c < n); c++) { for (const left of generateParenthesis(c)) { for (const right of generateParenthesis(((n - 1) - c))) { combos.push(`(${left})${right}`); } } } return combos Time: 2^N Space: 2^N
Deletion Distance algorithm. Time and Space
const dp = Array(str1.length + 1).fill().map(() => Array(str2.length + 1)); for (let i = 0; i < str1.length + 1; i++) { for (let j = 0; j < str2.length + 1; j++) { if (i === 0) { dp[i][j] = j; } else if (j === 0) { dp[i][j] = i; } else if (str1[i - 1] === str2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1]); }}} return dp[str1.length][str2.length]; Time: O(m * n). (m and n length of input strings) Space: O(m* n) (m and n length of input strings)
Two Sum Algorithm
const hashTable = new Map(); for (let i = 0; i < nums.length; i ++) { let diff = target - nums[i]; if(hashTable.has(diff)) { return [hashTable.get(diff), i] } else { hashTable.set(nums[i], i) } }
Group Anagram Algorithm (Time and Space)
const hashTable = {}; for (let i = 0; i < strs.length; i++) { const key = strs[i].split('').sort().join('') if (hashTable[key] !== undefined) { hashTable[key].push(strs[i]) } else { hashTable[key] = [strs[i]]; } } let output = [] for (let key in hashTable) { output.push(hashTable[key]) } return output Time: O(n) Space: O(n)
Is SubTree Algorithm
const isSubtree = (root, subRoot) => { const areEqual = (node1, node2) => { if (!node1 || !node2) return !node1 && !node2; if (node1.val !== node2.val) return false; return areEqual(node1.left, node2.left) && areEqual(node1.right, node2.right); } const dfs = (node) => { if (!node) return false; if (areEqual(node, subRoot)) return true; return dfs(node.left) || dfs(node.right); } return dfs(root); };
Product of Array Except Self Algorithm (Time and Space)
const result = []; let prefix = 1; let postfix = 1; for (let i = 0; i < nums.length; i++) { result[i] = prefix; prefix *= nums[i]; } for (let i = nums.length - 2; i >= 0; i--) { postfix *= nums[i + 1]; result[i] *= postfix; } return result; }; Time: O(n) Space: O(n)
Three Sum Algorithm
const results = []; if (nums.length < 3) return results; nums = nums.sort((a, b) => a - b); for (let i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] === nums[i - 1]) continue; let left = i + 1; let right = nums.length - 1; while (left < right) { let sum = nums[i] + nums[left] + nums[right]; if (sum === 0) { results.push([nums[i], nums[left], nums[right]]); while (nums[left] === nums[left + 1]) left++; while (nums[right] === nums[right - 1]) right--; left++; right--; } else if (sum < 0) { left++; } else { right--; } } } return results; };
Word Count Algorithm (convert to lower case, remove punctuation, sort by occurrences, if occurrences are equal, sort by original order). Time and Space
const splitArr = document.split(' '); const map = new Map(); for (let i = 0; i < splitArr.length; i++) { let word = splitArr[i].toLowerCase().replace(/[^a-z]/g, ''); if (word.length < 1) continue; if (map.has(word)) map.get(word).count++; else {map.set(word, { count: 1, index: i }); } } const sortedMap = [...map.entries()].sort((a, b) => { if (a[1].count === b[1].count) return a[1].index - b[1].index; else {return b[1].count - a[1].count;} }); return sortedMap.map(([word, data]) => [word, String(data.count)]); Time: O(N) Space: O(M) (M being total unique words)
Kth Smallest Element in BST Algo
const stack = [ ]; let i = 0; let cur = root; while (cur || stack.length) { while (cur) { stack.push(cur); cur = cur.left; } cur = stack.pop(); i += 1; if (i == k) return cur.val cur = cur.right; } // Time O(N + K) | Space O(H)
Evaluate Reverse Polish Notation Algo (Space & Time)
const stack = []; const mathFunctions = { '+': (a, b) => a + b, '-': (a, b) => a - b, '*': (a, b) => a * b, '/': (a, b) => Math.trunc(a / b) }; for (const char of tokens) { const isOperator = char in mathFunctions if (isOperator) { const [a, b] = [stack.pop(), stack.pop()]; const result = mathFunctions[char](b,a) stack.push(result); } else { stack.push(Number(char)); } } return stack[0]; Space: O(N) Time: O(N)
Smallest Substring of All Characters Algo (Pramp)
const targetMap = new Map(); for (const char of arr) { targetMap.set(char, (targetMap.get(char) || 0) + 1); } let minWindow = ""; let minWindowLength = Infinity; let left = 0; let count = 0; for (let right = 0; right < str.length; right++) { const rightChar = str[right]; if (targetMap.has(rightChar)) { targetMap.set(rightChar, targetMap.get(rightChar) - 1); if (targetMap.get(rightChar) >= 0) { count++; } } while (count === arr.length) { if (right - left + 1 < minWindowLength) { minWindowLength = right - left + 1; minWindow = str.substring(left, right + 1); } const leftChar = str[left]; if (targetMap.has(leftChar)) { targetMap.set(leftChar, targetMap.get(leftChar) + 1); if (targetMap.get(leftChar) > 0) { count--; } } left++; } } return minWindow; Time: O(N) Space: O(M) M being length of input substring
Min Cost Climbing Stairs Algorithm (Time & Space)
cost.push(0); for ( let i = cost.length - 3; i > -1; i --) { cost[i] += Math.min(cost[i + 1],cost[i + 2]) } return Math.min(cost[0], cost[1]) Time: O(N) Space: O(1)
Longest Substring W/O Repeating Char Algorithm (Time & Space)
create set; init start ptr (0); init max (0) iterate over string if ltr in set move start ptr up to beginning of new substring (immediately after the ltr in set) delete first char (start ptr) of set until set no longer has ltr each time, move start ptr up else/ then, add current ltr to set set max to greater of max or the size of the set const set = new Set(); let l = 0; let max = 0; for (let r = 0; r < s.length; r++) { while (set.has(s[r])) { set.delete(s[l]); l++; } set.add(s[r]); max = Math.max(max, set.size); } return max; Space: O(N) Time: (N)
Array-Based Heap Algorithm
function buildMinHeap(arr) { for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) { heapifyDownMin(arr, i, arr.length); }} function heapifyDownMin(arr, index, heapSize) { while (true) { const leftChildIndex = 2 * index + 1; const rightChildIndex = 2 * index + 2; let minIndex = index; if (leftChildIndex < heapSize && arr[leftChildIndex] < arr[minIndex]) { minIndex = leftChildIndex; } if (rightChildIndex < heapSize && arr[rightChildIndex] < arr[minIndex]) { minIndex = rightChildIndex; } if (minIndex !== index) { [arr[index], arr[minIndex]] = [arr[minIndex], arr[index]]; index = minIndex; } else {break;}}} function extractMin(arr) { const min = arr[0]; arr[0] = arr.pop(); heapifyDownMin(arr, 0, arr.length); return min; } // Invert heapifyDownMin comparison conditions to get max heap
Basic Regex Parser Algo (Time & Space)
function isMatch(text, pattern) { let t = 0; let p = 0; while (p < pattern.length ) { if (p < pattern.length - 1 && pattern[p+1] === '*') { if (pattern[p] === '.' || pattern[p] === text[t]) { t++; } else { p += 2; } } else { if (pattern[p] !== '.' && pattern[p] !== text[t]) { return false; } t++; p++; } if (t === text.length && p < pattern.length - 1 && pattern[p+1] === '*') { p += 2; } } return t === text.length; }
Merge Sort Algorithm (time, space, use case)
function merge(left, right) { let sortedArr = [] while (left.length && right.length) { if (left[0] < right[0]) { sortedArr.push(left.shift()) } else { sortedArr.push(right.shift()) } } return [...sortedArr, ...left, ...right] } function mergeSort(arr) { if (arr.length <= 1) return arr let mid = Math.floor(arr.length / 2) let left = mergeSort(arr.slice(0, mid)) let right = mergeSort(arr.slice(mid)) return merge(left, right) } Time: O(n logn) Space: O(n)
Quick Select Algorithm (Space & Time)
function quickSelect(arr, left, right, k) { if (left === right) { return arr[left]; } let pivotIndex = partition(arr, left, right); if (k === pivotIndex) { return arr[k]; } else if (k < pivotIndex) { return quickSelect(arr, left, pivotIndex - 1, k); } else { return quickSelect(arr, pivotIndex + 1, right, k); }} function partition(arr, left, right) { let pivotIndex = Math.floor(Math.random() * (right - left + 1)) + left; let pivotValue = arr[pivotIndex]; swap(arr, pivotIndex, right); let i = left; for (let j = left; j < right; j++) { if (arr[j] < pivotValue) { swap(arr, i, j); i++; } } swap(arr, i, right); return i; } Space: O(1) Time: O(n) -- average -- worst: O(n^2) if pivot poorly chosen Use Case: Find the Kth-Smallest Element in an unsorted list
Shifted Array Search Algorithm (Time & Space)
function shiftedArrSearch(shiftArr, num) { const pivot = findPivot(shiftArr); if (pivot === 0 || num < shiftArr[0]){ return binarySearch(shiftArr, pivot, shiftArr.length - 1, num) } else{ binarySearch(shiftArr, 0, pivot - 1, num);}} function binarySearch(arr, begin, end, num) { //binary search } return -1;} function findPivot(arr) { let start = 0; let end = arr.length - 1; while (start <= end) { const mid = Math.floor((end + start) / 2); if (mid === 0 || arr[mid] < arr[mid - 1]) return mid; if (arr[mid] > arr[0]) start = mid + 1; else end = mid - 1; } return 0;} Scan array until we find index where i-1 > i, meaning thats the point where it was shifted if mid gets to 0, there was no shift, return if mid-1 > mid, we found the shift if mid is greater than index 0, increment mid otherwise, decrement mid if not found, return 0 if not shifted or our number is less than array[0] (meaning the array is shifted and it must be in the other half), binary search mid to end otherwise, binary search beginning to mid Time : O(log(n)) Space: O(1)
Reverse Linked List (recursive algorithm)
if (!head || !head.next ) { return head } let reversedHead = reverseList(head.next); head.next.next = head; head.next = null return reversedHead
Quicksort Algorithm (& run time & comparison to merge sort)
if (array.length < 2) return array else pivot= array[0] less = array.map((el)=> el <= pivot) greater = array.map((el)=> el > pivot) return quicksort(less) + [pivot] + quicksort(greater). O(n log n) runtime average as long as pivot = random element
Climbing Stairs/ Fibonacci Algorithm (Time & Space)
if (n < 2) { return 1; } let firstStep = 1; let secondStep = 1; let thirdStep = 0; for (let i = 2; i <= n; i++) { thirdStep = firstStep + secondStep; firstStep = secondStep; secondStep = thirdStep; } return thirdStep; Time: O(N) Space: O(1)
Array Quadruplet Algorithm
if (nums.length < 4) return []; nums = nums.sort((a, b) => a - b); for (let i = 0; i < nums.length - 3; i++) { if (i > 0 && nums[i] === nums[i - 1]) continue; for (let j = i + 1; j < nums.length - 2; j++) { if (j > i + 1 && nums[j] === nums[j - 1]) continue; let left = j + 1; let right = nums.length - 1; while (left < right) { let sum = nums[i] + nums[j] + nums[left] + nums[right]; if (sum === target) { return [nums[i], nums[j], nums[left], nums[right]]; } else if (sum < target) { left++; } else { right--; }}}} return [];
Invert Binary Tree Algorithm
if (root == null) return null temp = root.left root.left = root.right root.right = temp invertTree(root.left) invertTree(root.right) return root
Max Depth Binary Tree Algorithm
if (root === null) return 0 return 1 + Math.max( maxDepth(root.left), maxDepth(root.right) );
Valid Anagram Algorithm
if (s.length !== t.length) { return false } for (let i = 0; i < s.length; i++) { const char = s[i] const code = char.charCodeAt(0); if (code in cache) { cache[code] = cache[code] += 1; } else { cache[code] = 1; } } for (let i = 0; i < s.length; i++) { const char = t[i] const code = char.charCodeAt(0); if (code in cache) { cache[code] = cache[code] -= 1 } else { return false } } return Object.values(cache).every(val => val === 0) }
Permutation in String Algo (Space and Time)
if (s1.length > s2.length) return false; const s1Chars = new Array(26).fill(0); const s2Chars = new Array(26).fill(0); let matches = 0; //populating initial window for each string for (let i = 0; i < s1.length; i++) { s1Chars[s1.charCodeAt(i) - 97]++; s2Chars[s2.charCodeAt(i) - 97]++; } //getting match count for initial window for (let i = 0; i < 26; i++) { if (s1Chars[i] === s2Chars[i]) matches++; } for (let i = 0; i < s2.length - s1.length; i++) { if (matches === 26) return true; const leftChar = s2.charCodeAt(i) - 97; const rightChar = s2.charCodeAt(i + s1.length) - 97; //add right char to dict s2Chars[rightChar]++; //if that made it a match, inc if (s2Chars[rightChar] === s1Chars[rightChar]) matches++; //if that made it not a match, dec else if (s2Chars[rightChar] === s1Chars[rightChar] + 1) matches--; //remove left char from dict s2Chars[leftChar]--; if (s2Chars[leftChar] === s1Chars[leftChar]) matches++; else if (s2Chars[leftChar] === s1Chars[leftChar] - 1) matches--; } //check if final add and subtract found a match return matches === 26; } Space: O(1) Time: O(N + M) s1.length + s2.length
Binary Search Algorithm
let [low, high] = [0, nums.length - 1]; while (low <= high) { let mid = Math.floor((low + high) / 2); if( nums[mid] === target) { return mid } if (nums[mid] > target) { high = mid -1; } else { low = mid + 1 } } return -1 O( log n)
Hamming Weight Algorithm
let count = 0; while (int !== 0) { const bitComparison = int & 1; if (bitComparison === 1) count++; int >>>= 1; } return count;
Balanced Binary Tree Algorithm
let dfs = function(node) { if (!node) return 0; let left = 1 + dfs(node.left); let right = 1 + dfs(node.right); if (Math.abs(left - right) > 1) return Infinity; return Math.max(left, right); } return dfs(root)==Infinity?false:true;
Diameter of Binary Tree Algorithm
let diameter = 0; dfs(root); return diameter; function dfs(node) { if (!node) return 0; const left = dfs(node.left); const right = dfs(node.right); diameter = Math.max(diameter, left + right); return 1 + Math.max(left, right); }
Insertion Sort
let i, key, j; for (i = 1; i < arr.length; i++) { key = arr[i]; j = i - 1; while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j = j - 1; } arr[j + 1] = key; }
Longest Consecutive Algorithm (Time & Space)
let maxScore = 0; const numSet = new Set(nums); for (const num of [ ...numSet ]) { if (numSet.has(num - 1)) continue; let [ currNum, score ] = [ num, 1 ]; const isStreak = () => numSet.has(currNum + 1) while (isStreak()) { currNum++; score++; } maxScore = Math.max(maxScore, score); } return maxScore; make a set. intialize longest = 0 iterate through if the integer is the start of a sequence (set does not have int - 1) intialize length = 0 see if it has a number after it if it does, increment length and check for the next number do so until reaching the end of the string (int + 1 dne) see if the length of the string is the greatest so far if so, update maxLength Time: O(N) Space: O(N)
Move Zeroes to End Algorithm (Time & Space)
let right = 0; let left = 0; let temp; while (right < arr.length ) { if (arr[left] !== 0) { left ++ } else if (arr[right] !== 0 && arr[left] == 0) { swap right & left left ++ } right ++ } return arr Use two pointers. One increments until it finds a zero. The other increments every time. If pointer one (zeroptr) is at a zero, and pointer two is at a non-zero, swap them and increment pointer one (zeroptr). Increment pointer two regardless. Do so until pointer two reaches end of array. Space: O(1) Time: O(n)
Contains Duplicate Algorithm
let seen = {}; for (let i = 0; i < nums.length; i++) { let num = nums[i]; if (seen[num]) { return true; } else { seen[num] = true; } } return false
Merge Two Sorted Lists Algorithm
output = new Node, ptr = output, while list1 && list2, if list1.val < list2.val, output.next = new Node(list1.val) list1 = list1.next; else output.next = new Node(list2.val) list2 = list2.next output = output.next if (list1 && !list2) { output.next = list1 } if (list2 && !list1) { output.next = list2 } return temp.next
Breadth First Search (BFS) Algorithm (use case & time)
queue = []; queue.push(root_item) searched = {} while queue.length > 0 { item = queue.shift(); if (item not in searched) { if (item is target) { return item else { item.children.forEach((child)=>queue.push(child)) searched[item] = true return false Time: O(V+E) # of vertices, edges Use case: Calculate shortest path for an unweighted graph
Single Number Algorithm (constant space, linear time, all numbers but one are twins)
return nums.reduce((prev, curr) => prev ^ curr);
Bitwise AND (&)
returns a number whose binary representation has a 1 in each bit position for which the corresponding bits of both operands are 1
Bitwise XOR (^)
returns a number whose binary representation has a 1 in each bit position for which the corresponding bits of either but not both operands are 1.
Valid Parentheses Algorithm
use stack and map. convert string to array map keys are open paren, values close paren. if open paren, push to stack. if close paren, pop from stack and see if popped key's value equals current value return stack.length === 0
Copy List with Random Pointers Algo
var copyRandomList = function(head) { /** Pass one: Deep copy of list nodes. Map each copy node to the original node in a hash map. Pass two: Link nodes */ const map = new Map(); // to handle pointers to null map.set(null, null) let ptr = head; while (ptr) { let copy = new Node(ptr.val); map.set(ptr, copy); ptr = ptr.next; } ptr = head; while (ptr) { let copyNode = map.get(ptr); copyNode.next = map.get(ptr.next); copyNode.random = map.get(ptr.random); ptr = ptr.next; } return map.get(head); }; Space: O(N) Time: O(N)
Same Tree Algorithm
var isSameTree = function(p, q) { if (p === null && q === null) { return true; } if (p === null || q === null) { return false; } if (p.val !== q.val) { return false; } return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); }
Validate BST Algo
var isValidBST = function(root, min = -Infinity, max = Infinity) { if (root === null) return true; if (root.val <= min || max <= root.val) return false; return dfs(root, min, max); }; const dfs = (root, min, max) => { const left = isValidBST(root.left, min, root.val); const right = isValidBST(root.right, root.val, max); return left && right; } Time O(N) | Space O(H)
Level Order Traversal Algorithm
var levelOrder = function (root) { if (!root) return [] let queue = []; let output = []; let lvlCount = 0; queue.push(root); while (queue.length > 0) { let queueSnapshot = queue.length output.push([]); for (let i = 0; i < queueSnapshot; i++) { let item = queue.shift(); output[lvlCount].push(item.val) if (item.left) { queue.push(item.left) } if (item.right) { queue.push(item.right) } } lvlCount += 1; } return output };
Container With Most Water Algorithm (Time and Space)
var maxArea = function (height) { let [left, right, max] = [0, height.length - 1, 0]; while (left < right) { let containerHeight, area; let containerWidth = right - left; if (height[left] < height[right]) { containerHeight = height[left]; left++; } else { containerHeight = height[right]; right--; } area = containerWidth * containerHeight; if (area > max) { max = area; } } return max; }; Time: O(n) Space: O(1)