├── README.md ├── bfs.js ├── binary-search.md ├── binary-tree-level-order-traversal.md ├── binary-tree-right-side-view.md ├── can-reach-leaf-node.md ├── combination-sum.md ├── construct-binary-tree-from-preorder-and-inorder-traversal.md ├── contains-duplicate.md ├── delete-node-in-a-bst.md ├── design-linked-list.md ├── insert-into-a-binary-search-tree.md ├── koko-eating-bananas.md ├── kth-largest-element-in-a-stream.md ├── kth-largest-element-in-an-array.md ├── kth-smallest-element-in-a-bst.md ├── last-stone-weight.md ├── lru-cache.md ├── max-area-of-island.md ├── merge-two-sorted-lists.md ├── number-of-islands.md ├── path-sum.md ├── reverse-linked-list.md ├── search-a-2d-matrix.md ├── search-in-a-binary-search-tree.md ├── shortest-path-in-binary-matrix.md ├── subsets.md └── two-sum.md /README.md: -------------------------------------------------------------------------------- 1 | # My LeetCode notes 2 | 3 | I'll be sharing my leetcode notes here for some of the solutions. 4 | -------------------------------------------------------------------------------- /bfs.js: -------------------------------------------------------------------------------- 1 | function bfs(grid) { 2 | let ROWS = grid.length; 3 | let COLS = grid[0].length; 4 | let visited = new Set([`0,0`]); 5 | let queue = [[0, 0]]; 6 | let steps = 0; 7 | 8 | while (queue.length) { 9 | let length = queue.length; 10 | 11 | for (let i = 0; i < length; i++) { 12 | const [row, col] = queue.shift(); 13 | 14 | if (row === ROWS - 1 && col === COLS - 1) return steps; 15 | 16 | const neighbours = [ 17 | [0, 1], 18 | [1, 0], 19 | [0, -1], 20 | [-1, 0], 21 | ]; 22 | 23 | for (let [rowNeighbour, colNeighbour] of neighbours) { 24 | const newRow = row + rowNeighbour; 25 | const newCol = col + colNeighbour; 26 | const key = `${newRow},${newCol}`; 27 | 28 | if (Math.min(newRow, newCol) < 0) continue; 29 | if (newRow === ROWS || newCol === COLS) continue; 30 | if (grid[newRow][newCol] === 1) continue; 31 | if (visited.has(key)) continue; 32 | 33 | visited.add(key); 34 | queue.push([newRow, newCol]); 35 | } 36 | } 37 | 38 | steps++; 39 | } 40 | } 41 | 42 | const grid = [ 43 | [0, 0, 0], 44 | [0, 0, 0], 45 | [0, 0, 0], 46 | ]; 47 | 48 | console.log(bfs(grid)); 49 | -------------------------------------------------------------------------------- /binary-search.md: -------------------------------------------------------------------------------- 1 | # Binary Search 2 | 3 | ```js 4 | /** 5 | * @param {number[]} nums 6 | * @param {number} target 7 | * @return {number} 8 | */ 9 | var search = function (nums, target) { 10 | let L = 0; 11 | let R = nums.length - 1; 12 | 13 | while (L <= R) { 14 | const mid = Math.floor((L + R) / 2); 15 | 16 | if (target > nums[mid]) { 17 | L = mid + 1; 18 | } else if (target < nums[mid]) { 19 | R = mid - 1; 20 | } else { 21 | return mid; 22 | } 23 | } 24 | 25 | return -1; 26 | }; 27 | ``` 28 | 29 | Binary search is a simple one, we just need to keep track of the left and right pointers, and then we can just keep moving them until we find the target 30 | 31 | the time complexity here is log n because we are halving the search space each time 32 | 33 | we wanna do L <= R, because if we have a single element, we want to check it still, since that could be the target! 34 | 35 | otherwise we just check greater or less than to determine where to move the pointers or if we found the target! 36 | -------------------------------------------------------------------------------- /binary-tree-level-order-traversal.md: -------------------------------------------------------------------------------- 1 | # Binary Tree Level Order Traversal 2 | 3 | ```js 4 | /** 5 | * Definition for a binary tree node. 6 | * function TreeNode(val, left, right) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.left = (left===undefined ? null : left) 9 | * this.right = (right===undefined ? null : right) 10 | * } 11 | */ 12 | /** 13 | * @param {TreeNode} root 14 | * @return {number[][]} 15 | */ 16 | var levelOrder = function (root) { 17 | if (!root) return []; 18 | 19 | let res = []; 20 | 21 | let queue = [root]; 22 | 23 | while (queue.length) { 24 | let length = queue.length; 25 | let currentLevel = []; 26 | 27 | for (let i = 0; i < length; i++) { 28 | let node = queue.shift(); 29 | currentLevel.push(node.val); 30 | 31 | if (node.left) queue.push(node.left); 32 | if (node.right) queue.push(node.right); 33 | } 34 | 35 | res.push(currentLevel); 36 | } 37 | 38 | return res; 39 | }; 40 | ``` 41 | 42 | haha 43 | 44 | the famous bfs question that went viral on twitter because someone said 90% of people can't solve it 45 | 46 | well, that might be true lol, also it is crazy that people forget how to write a simple for loop 47 | 48 | bfs is interesting 49 | 50 | now to be clear, the first check is there in case someone calls the function with null 51 | 52 | the key to bfs is having a queue 53 | 54 | this queue starts with the root node 55 | 56 | we continue our loop while the queue is not empty 57 | 58 | the trick tho is to keep adding to the queue, that's the interesting part, how it length is kept track of as its attached to queue 59 | 60 | just as a PS, arrays are objects under the hood 61 | 62 | for every level, we get a snapshot of the length, since this will change as we add to the queue 63 | 64 | we loop through the length, and for each node, we add its value to the current level array 65 | 66 | we also add its left and right children to the queue 67 | 68 | queue and current level are different 69 | 70 | current level is reset for every level 71 | 72 | queue is the same, we just keep adding to it, causing the next level to be processed as the queue.length isn't empty yet 73 | 74 | its important to add it node.left before node.right, because we want to process the left child first, since we're processing it by order, level by level 75 | -------------------------------------------------------------------------------- /binary-tree-right-side-view.md: -------------------------------------------------------------------------------- 1 | # Binary Tree Right Side View 2 | 3 | ```js 4 | /** 5 | * Definition for a binary tree node. 6 | * function TreeNode(val, left, right) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.left = (left===undefined ? null : left) 9 | * this.right = (right===undefined ? null : right) 10 | * } 11 | */ 12 | /** 13 | * @param {TreeNode} root 14 | * @return {number[]} 15 | */ 16 | var rightSideView = function (root) { 17 | if (!root) return []; 18 | 19 | let res = []; 20 | let queue = [root]; 21 | 22 | while (queue.length) { 23 | let length = queue.length; 24 | let rightMostNode = null; 25 | 26 | for (let i = 0; i < length; i++) { 27 | let node = queue.shift(); 28 | rightMostNode = node; 29 | 30 | if (node.left) queue.push(node.left); 31 | if (node.right) queue.push(node.right); 32 | } 33 | 34 | res.push(rightMostNode.val); 35 | } 36 | 37 | return res; 38 | }; 39 | ``` 40 | 41 | if you've not seen the first bfs question, go through that one 42 | 43 | essentially we know that we're pushing left in first if it exists 44 | 45 | because we're getting the first element with .shift 46 | 47 | we know that as long as for each level, we keep assignign the latter node to the rightmostnode, we'll keep going right since we always push the right node AFTER the left node 48 | -------------------------------------------------------------------------------- /can-reach-leaf-node.md: -------------------------------------------------------------------------------- 1 | ```js 2 | // Leaf node without encountering0 3 | function canReachLeaf(root) { 4 | // If we encounter zero or null e.g. if root.left from parent is null 5 | // return false from this path 6 | if (!root || root.val === 0) return false; 7 | 8 | // if not zero and it exists, check if it's a leaf 9 | // if it is, wohoo, we found a leaf 10 | if (!root.left && !root.right) return true; 11 | 12 | // Try going down the left path first 13 | if (canReachLeaf(root.left)) return true; 14 | 15 | // If we can't find a leaf on the left, try the right path 16 | // The interesting part here is that we'll try the right path if root.left returned false 17 | // So we won't just immediately return false all the way back up 18 | if (canReachLeaf(root.right)) return true; 19 | 20 | // we return false all the way back up from a node only if 21 | // we've tried both left and right paths of a root and they both returned false 22 | return false; 23 | } 24 | ``` 25 | 26 | ``` 27 | 4 28 | / \ 29 | 2 6 30 | / \ 31 | 1 0 32 | ``` 33 | 34 | ```js 35 | canReachLeaf(4) // Start at root (4) 36 | // 4 != 0 and root exists, continue 37 | // Node has children so not a leaf 38 | └── Try left: canReachLeaf(2) // Check left subtree 39 | // 2 != 0 and node exists, continue 40 | // Has left child (1), not a leaf 41 | └── Try left: canReachLeaf(1) // Check left child 42 | // 1 != 0 and node exists, continue 43 | // No children - this is a leaf! 44 | └── return True // Found valid leaf! 45 | └── return True // Propagate True up 46 | └── return True // Final result True 47 | 48 | // Note: Right subtree (6->0) never explored 49 | // because we found valid path on left! 50 | ``` 51 | 52 | ```js 53 | canReachLeaf(6) 54 | // 6 != 0 and exists, continue 55 | // Has right child (0), not a leaf 56 | └── Try right: canReachLeaf(0) 57 | // val == 0, immediately return False 58 | └── Try left: no left child 59 | └── return False // No valid path found 60 | ``` 61 | -------------------------------------------------------------------------------- /combination-sum.md: -------------------------------------------------------------------------------- 1 | # Combination Sum 2 | 3 | ```js 4 | /** 5 | * @param {number[]} candidates 6 | * @param {number} target 7 | * @return {number[][]} 8 | */ 9 | var combinationSum = function (candidates, target) { 10 | let res = []; 11 | 12 | function dfs(i, currentRes, totalSum) { 13 | if (totalSum === target) { 14 | res.push(currentRes.slice()); 15 | return; 16 | } 17 | 18 | if (i === candidates.length || totalSum > target) { 19 | return; 20 | } 21 | 22 | currentRes.push(candidates[i]); 23 | 24 | dfs(i, currentRes, totalSum + candidates[i]); 25 | 26 | currentRes.pop(); 27 | 28 | dfs(i + 1, currentRes, totalSum); 29 | } 30 | 31 | dfs(0, [], 0); 32 | return res; 33 | }; 34 | ``` 35 | 36 | this one was tricky 37 | 38 | i spent like triple the time on it compared to subset and path sum combined 39 | 40 | it was tricky because it felt like a different mental model where we do the checks and then we do the adding + dfs calls 41 | 42 | the problem is that we wanna find all the possible combinations that add up to the target, and we wanna return an array containing all the combinations, so an array of arrays 43 | 44 | the base cases should be understandable 45 | 46 | if total sum is target, we know not to continue the recursion, motherwise it'd be too much 47 | 48 | if i is out of bounds or total sum is greater than target, we stop as well 49 | 50 | we then wanna continue exhausting the first path, so we add the current candidate to the currentRes array, and we call dfs again with the same index, this way we're exhausting the first path 51 | 52 | when we return back up 53 | 54 | we pop 55 | 56 | and we take the next candidate, i think its worth mentioning that the same thing will happen as we move back up the call stack 57 | 58 | when we take the next candidate 59 | 60 | we actually need to do the same thing for the next candidate 61 | 62 | when we call `dfs(i + 1, currentRes, totalSum)` nothing will happen 63 | 64 | the real deal is when we call `dfs(i, currentRes, totalSum + candidates[i])`, that's when we actually try to find the next path 65 | 66 | let's do a small example 67 | 68 | we have the candidates array `[2,3,7]` and the target is `7` 69 | 70 | we start with `dfs(0, [], 0)` 71 | 72 | we add `2` to the currentRes array, and we call `dfs(0, [2], 2)` 73 | 74 | we add `2` to the currentRes array, and we call `dfs(0, [2,2], 4)` 75 | 76 | we add `2` to the currentRes array, and we call `dfs(0, [2,2,2], 6)` 77 | 78 | we add `2` to the currentRes array, and we call `dfs(0, [2,2,2,2], 8)` (THE KEY HERE IS UNDERSTANDING THAT WE CAN KEEP EXHAUSTING THE SAME NUMBER OVER AND OVER TILL WE HIT SUM OR ITS GREATER THAN SUM) 79 | 80 | this doesn't work, its too much 81 | 82 | we return back up the call stack, we pop the last `2` 83 | 84 | we call `dfs(1, [2,2,2], 6)` 85 | 86 | we add `3` to the currentRes array, and we call `dfs(1, [2,2,2,3], 9)` 87 | 88 | wont work, too much, we return back up the call stack 89 | 90 | we return back up the call stack, we pop the last `3` 91 | 92 | we call `dfs(2, [2,2,2], 6)` 93 | 94 | we add `7` to the currentRes array, and we call `dfs(2, [2,2,2,7], 13)` 95 | 96 | too much, we return back up the call stack, we pop the last `7` 97 | 98 | we return back up the call stack, we pop the last `7` 99 | 100 | we call `dfs(3, [2,2,2], 6)` 101 | 102 | this is out of bounds, we return back up the call stack 103 | 104 | this is where it gets exciting 105 | 106 | now we pop the last `2` 107 | 108 | if i show the function with some comments to explain where we're at 109 | 110 | ```js 111 | function dfs(i, currentRes, totalSum) { 112 | if (totalSum === target) { 113 | res.push(currentRes.slice()); 114 | return; 115 | } 116 | 117 | if (i === candidates.length || totalSum > target) { 118 | return; 119 | } 120 | 121 | currentRes.push(candidates[i]); 122 | 123 | // now in the new one, we called with this index as 2 124 | // so we add 7 to it 125 | // this was too much, so we return back up the call stack 126 | dfs(i, currentRes, totalSum + candidates[i]); 127 | 128 | // we pop the last 7 129 | currentRes.pop(); 130 | 131 | // PREVIOUS: in the previous one 132 | // PREVIOUS: we popped 3 and moved forward to 7 133 | 134 | // now for 7, we popped it and tried to move forward 135 | // index 3 is out of bounds 136 | // we only have 3 candidates 137 | // so we return back up 138 | dfs(i + 1, currentRes, totalSum); 139 | } 140 | ``` 141 | 142 | if we return back up from 7, where are we now? 143 | 144 | this is the exciting bit 145 | 146 | ```js 147 | function dfs(i, currentRes, totalSum) { 148 | if (totalSum === target) { 149 | res.push(currentRes.slice()); 150 | return; 151 | } 152 | 153 | if (i === candidates.length || totalSum > target) { 154 | return; 155 | } 156 | 157 | currentRes.push(candidates[i]); 158 | 159 | dfs(i, currentRes, totalSum + candidates[i]); 160 | 161 | // here, we'll continue 162 | // because we already popped like oyu saw in the last snippet 163 | // we popped and then we returned back up becuase out of bounds 164 | // we have [2, 2, 2] here 165 | // and the index is still 0 166 | currentRes.pop(); 167 | 168 | // now its [2, 2] 169 | // and index is 1 170 | // dfs(1, [2, 2], 4) 171 | dfs(i + 1, currentRes, totalSum); 172 | 173 | // the exciting bit here is that we'll just exhaust and do the same pattern over again 174 | // this time, 3 will be in index 2 and we'll start with [2,2,3] 175 | } 176 | ``` 177 | -------------------------------------------------------------------------------- /construct-binary-tree-from-preorder-and-inorder-traversal.md: -------------------------------------------------------------------------------- 1 | # Construct Binary Tree from Preorder and Inorder Traversal 2 | 3 | ```js 4 | /** 5 | * Definition for a binary tree node. 6 | * function TreeNode(val, left, right) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.left = (left===undefined ? null : left) 9 | * this.right = (right===undefined ? null : right) 10 | * } 11 | */ 12 | /** 13 | * @param {number[]} preorder 14 | * @param {number[]} inorder 15 | * @return {TreeNode} 16 | */ 17 | var buildTree = function (preorder, inorder) { 18 | if (!preorder.length || !inorder.length) return null; 19 | 20 | let rootVal = preorder[0]; 21 | let root = new TreeNode(rootVal); 22 | let mid = inorder.indexOf(rootVal); 23 | 24 | root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid)); 25 | root.right = buildTree(preorder.slice(mid + 1), inorder.slice(mid + 1)); 26 | 27 | return root; 28 | }; 29 | ``` 30 | 31 | this one is really tricky 32 | 33 | its about understanding the role of mid 34 | 35 | well first we gotta understrand preorder and inorder 36 | 37 | inorder goes from smallest to largest 38 | 39 | preorder goes from root to left to right 40 | 41 | the first one in preorder is the root always 42 | 43 | here we create a node with the root value 44 | 45 | we find the index of the root in inorder, this tells us how many nodes are on the left and right side of the root 46 | 47 | with this index, we call it mid, with this one, we can know how many nodes are to the left of the root 48 | 49 | when we recursively call it for the left side, on preorder, we gotta get everything from the next index, not the root since we already used it, to the mid index, we wanna include the mid index here 50 | 51 | .slice is up to but not including, we wanna include it because the mid index is among the left side, for the inorder, we want everything up to the mid index, we dont wanna include it because its the root, but we know that everything up to it is on the left side 52 | 53 | for the right one, we get everything from the mid index + 1 54 | 55 | for preorder this is because everything from the mid index + 1 is on the right side 56 | 57 | for inorder, we dont use mid because that'd include the root, so we get everything from the mid + 1 to get the right side 58 | -------------------------------------------------------------------------------- /contains-duplicate.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * @param {number[]} nums 4 | * @return {boolean} 5 | */ 6 | var containsDuplicate = function (nums) { 7 | let hash = {}; 8 | 9 | for (let i = 0; i < nums.length; i++) { 10 | if (hash[nums[i]]) return true; 11 | 12 | hash[nums[i]] = true; 13 | } 14 | 15 | return false; 16 | }; 17 | ``` 18 | 19 | this one is super simple 20 | 21 | we just check if number already exists in the hash map, if so return true, which breaks the loop and returns it out of the function 22 | 23 | otherwise we add it to the hash map 24 | 25 | if loop done, return false 26 | -------------------------------------------------------------------------------- /delete-node-in-a-bst.md: -------------------------------------------------------------------------------- 1 | # Delete Node in a BST 2 | 3 | ```js 4 | /** 5 | * Definition for a binary tree node. 6 | * function TreeNode(val, left, right) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.left = (left===undefined ? null : left) 9 | * this.right = (right===undefined ? null : right) 10 | * } 11 | */ 12 | /** 13 | * @param {TreeNode} root 14 | * @param {number} key 15 | * @return {TreeNode} 16 | */ 17 | var deleteNode = function (root, key) { 18 | if (!root) return null; 19 | 20 | if (key > root.val) { 21 | root.right = deleteNode(root.right, key); 22 | } else if (key < root.val) { 23 | root.left = deleteNode(root.left, key); 24 | } else { 25 | if (!root.left) return root.right; 26 | if (!root.right) return root.left; 27 | 28 | let minNode = findMinNode(root.right); 29 | 30 | root.val = minNode.val; 31 | 32 | root.right = deleteNode(root.right, minNode.val); 33 | } 34 | 35 | return root; 36 | }; 37 | 38 | function findMinNode(node) { 39 | let curr = node; 40 | 41 | while (curr.left) { 42 | curr = curr.left; 43 | } 44 | 45 | return curr; 46 | } 47 | ``` 48 | 49 | lets talk about deleting when we have a node with only one child 50 | 51 | then its easy, we just return the child that exists 52 | 53 | if no left, return right, if no right, return left 54 | 55 | EVEN if the other one doesnt exist too, then its gonna be null anyways, so thats fine 56 | 57 | if we hit a case where we dont find the node, we just return null 58 | 59 | we always need to return the root in the end, so we return the new subtree back up the chain to the parent which is waiting on it via root.left or root.right 60 | 61 | --- 62 | 63 | now lets talk about deleting a node with two children 64 | 65 | the question is which one to replace it with? 66 | 67 | we need the minimum from the right side 68 | 69 | because that would be greater than everything on the left side, and less than everything on the right side, we need to maintain the bst property 70 | 71 | then when done we delete the node we replaced it with, when we find it then, its gonna hit the case where it doesnt have a left child and we return the right child, if right child is null, thats fine, then its parent will just point to null which is correct as already covered 72 | -------------------------------------------------------------------------------- /design-linked-list.md: -------------------------------------------------------------------------------- 1 | # Design Linked List 2 | 3 | ```js 4 | class Node { 5 | constructor(val = null, prev = null, next = null) { 6 | this.val = val; 7 | this.prev = prev; 8 | this.next = next; 9 | } 10 | } 11 | 12 | var MyLinkedList = function () { 13 | this.dummyHead = new Node(); 14 | this.dummyTail = new Node(); 15 | 16 | this.dummyHead.next = this.dummyTail; 17 | this.dummyTail.prev = this.dummyHead; 18 | }; 19 | 20 | /** 21 | * @param {number} index 22 | * @return {number} 23 | */ 24 | MyLinkedList.prototype.get = function (index) { 25 | if (index < 0) return -1; 26 | 27 | let curr = this.dummyHead.next; 28 | 29 | while (index > 0 && curr) { 30 | curr = curr.next; 31 | 32 | index--; 33 | } 34 | 35 | if (!curr || curr === this.dummyTail) return -1; 36 | 37 | return curr.val; 38 | }; 39 | 40 | /** 41 | * @param {number} val 42 | * @return {void} 43 | */ 44 | MyLinkedList.prototype.addAtHead = function (val) { 45 | let prevHead = this.dummyHead.next; 46 | 47 | let newNode = new Node(val); 48 | 49 | this.dummyHead.next = newNode; 50 | 51 | newNode.prev = this.dummyHead; 52 | newNode.next = prevHead; 53 | 54 | prevHead.prev = newNode; 55 | }; 56 | 57 | /** 58 | * @param {number} val 59 | * @return {void} 60 | */ 61 | MyLinkedList.prototype.addAtTail = function (val) { 62 | let prevTail = this.dummyTail.prev; 63 | 64 | let newNode = new Node(val); 65 | 66 | this.dummyTail.prev = newNode; 67 | 68 | newNode.next = this.dummyTail; 69 | newNode.prev = prevTail; 70 | 71 | prevTail.next = newNode; 72 | }; 73 | 74 | /** 75 | * @param {number} index 76 | * @param {number} val 77 | * @return {void} 78 | */ 79 | MyLinkedList.prototype.addAtIndex = function (index, val) { 80 | if (index < 0) return; 81 | 82 | let curr = this.dummyHead.next; 83 | 84 | while (index > 0) { 85 | if (curr === this.dummyTail) return; 86 | 87 | curr = curr.next; 88 | index--; 89 | } 90 | 91 | let newNode = new Node(val); 92 | 93 | let prev = curr.prev; 94 | 95 | curr.prev = newNode; 96 | newNode.next = curr; 97 | newNode.prev = prev; 98 | prev.next = newNode; 99 | }; 100 | 101 | /** 102 | * @param {number} index 103 | * @return {void} 104 | */ 105 | MyLinkedList.prototype.deleteAtIndex = function (index) { 106 | if (index < 0 || this.dummyHead.next === this.dummyTail) return; 107 | 108 | let curr = this.dummyHead.next; 109 | 110 | while (index > 0) { 111 | if (curr === this.dummyTail) return; 112 | curr = curr.next; 113 | index--; 114 | } 115 | 116 | if (curr === this.dummyTail) return; 117 | 118 | let prev = curr.prev; 119 | let next = curr.next; 120 | 121 | prev.next = next; 122 | next.prev = prev; 123 | }; 124 | 125 | /** 126 | * Your MyLinkedList object will be instantiated and called as such: 127 | * var obj = new MyLinkedList() 128 | * var param_1 = obj.get(index) 129 | * obj.addAtHead(val) 130 | * obj.addAtTail(val) 131 | * obj.addAtIndex(index,val) 132 | * obj.deleteAtIndex(index) 133 | */ 134 | ``` 135 | 136 | this is a big one 137 | 138 | lets start with the first bit, why a dummy head and tail? this helps with edge cases such as adding at index 0 or deleting at index 0, otherwise we would have to check if the index is 0 and handle it differently, this way we can just use the same logic for all cases 139 | 140 | --- 141 | 142 | get 143 | 144 | ```js 145 | /** 146 | * @param {number} index 147 | * @return {number} 148 | */ 149 | MyLinkedList.prototype.get = function (index) { 150 | if (index < 0) return -1; 151 | 152 | let curr = this.dummyHead.next; 153 | 154 | while (index > 0 && curr) { 155 | curr = curr.next; 156 | 157 | index--; 158 | } 159 | 160 | if (!curr || curr === this.dummyTail) return -1; 161 | 162 | return curr.val; 163 | }; 164 | ``` 165 | 166 | if index is less than 0, return -1, its invalid 167 | 168 | we start at our real head which is dummy head's next, we can just keep iterating, we'll exit if we hit it or if curr is null 169 | 170 | if we exit, we know that index being 0 will only happy if curr doesnt exist 171 | 172 | if curr is null or is rqual to dummy tail, return -1, dummy tail means out of bounds 173 | 174 | otherwise return curr.val 175 | 176 | --- 177 | 178 | adding at head and tail 179 | 180 | ```js 181 | /** 182 | * @param {number} val 183 | * @return {void} 184 | */ 185 | MyLinkedList.prototype.addAtHead = function (val) { 186 | let prevHead = this.dummyHead.next; 187 | 188 | let newNode = new Node(val); 189 | 190 | this.dummyHead.next = newNode; 191 | 192 | newNode.prev = this.dummyHead; 193 | newNode.next = prevHead; 194 | 195 | prevHead.prev = newNode; 196 | }; 197 | 198 | /** 199 | * @param {number} val 200 | * @return {void} 201 | */ 202 | MyLinkedList.prototype.addAtTail = function (val) { 203 | let prevTail = this.dummyTail.prev; 204 | 205 | let newNode = new Node(val); 206 | 207 | this.dummyTail.prev = newNode; 208 | 209 | newNode.next = this.dummyTail; 210 | newNode.prev = prevTail; 211 | 212 | prevTail.next = newNode; 213 | }; 214 | ``` 215 | 216 | this is fairly straighforward, you just gotta make sure uve a pointer to the prev head or tail, then you can just update the pointers, otherwise you'd lose the reference to em both 217 | 218 | --- 219 | 220 | add at index and delete at index 221 | 222 | ```js 223 | /** 224 | * @param {number} index 225 | * @param {number} val 226 | * @return {void} 227 | */ 228 | MyLinkedList.prototype.addAtIndex = function (index, val) { 229 | if (index < 0) return; 230 | 231 | let curr = this.dummyHead.next; 232 | 233 | while (index > 0) { 234 | if (curr === this.dummyTail) return; 235 | 236 | curr = curr.next; 237 | index--; 238 | } 239 | 240 | let newNode = new Node(val); 241 | 242 | let prev = curr.prev; 243 | 244 | curr.prev = newNode; 245 | newNode.next = curr; 246 | newNode.prev = prev; 247 | prev.next = newNode; 248 | }; 249 | 250 | /** 251 | * @param {number} index 252 | * @return {void} 253 | */ 254 | MyLinkedList.prototype.deleteAtIndex = function (index) { 255 | if (index < 0 || this.dummyHead.next === this.dummyTail) return; 256 | 257 | let curr = this.dummyHead.next; 258 | 259 | while (index > 0) { 260 | if (curr === this.dummyTail) return; 261 | curr = curr.next; 262 | index--; 263 | } 264 | 265 | if (curr === this.dummyTail) return; 266 | 267 | let prev = curr.prev; 268 | let next = curr.next; 269 | 270 | prev.next = next; 271 | next.prev = prev; 272 | }; 273 | ``` 274 | 275 | these ones are a bit trickier 276 | 277 | when we add, its fine if the index is on the dummy tail, it means we should add at the end, however, if we realize before moving to curr.next, that curr is already the dummy tail, we should return, because we already know it's gonna be out of bounds 278 | 279 | after that we can just proceed as normal 280 | 281 | for deleting, we know that if head is dummy tail, we have nothing, so we can just return right away 282 | 283 | we can do the same check like adding, where if curr is dummy tail, we know it's out of bounds and we can return 284 | 285 | however, it can still happen, that curr is dummy tail, this is something we check outside the while loop, if this is also the case, we return, there is nothing to delte 286 | 287 | otherwise deletion is just taking prev and pointing it to next, and next and pointing it to prev, this effectively removes the node from the list 288 | -------------------------------------------------------------------------------- /insert-into-a-binary-search-tree.md: -------------------------------------------------------------------------------- 1 | # Insert into a BST 2 | 3 | ```js 4 | /** 5 | * Definition for a binary tree node. 6 | * function TreeNode(val, left, right) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.left = (left===undefined ? null : left) 9 | * this.right = (right===undefined ? null : right) 10 | * } 11 | */ 12 | /** 13 | * @param {TreeNode} root 14 | * @param {number} val 15 | * @return {TreeNode} 16 | */ 17 | var insertIntoBST = function (root, val) { 18 | if (!root) return new TreeNode(val); 19 | 20 | if (val > root.val) { 21 | root.right = insertIntoBST(root.right, val); 22 | } else if (val < root.val) { 23 | root.left = insertIntoBST(root.left, val); 24 | } 25 | 26 | return root; 27 | }; 28 | ``` 29 | 30 | this one is exciting, recursion, my jam 31 | 32 | so we need to eventually hit "null" in the right spot 33 | 34 | if root exists, we need to check whether we're going down the right or left side 35 | 36 | once we hit null, we'll create a new node and return it back up the chain to who ever called us with root.right or root.left 37 | 38 | in the end we need to return root 39 | 40 | the reason for this is because the parent up the chain will need the subtree returned, so that it has the right subtree 41 | 42 | ``` 43 | 4 44 | / \ 45 | 2 6 46 | / \ \ 47 | 1 3 7 48 | ``` 49 | 50 | lets say we gotta insert 5 51 | 52 | ```js 53 | insertIntoBST(4, 5) // Start at root (4) 54 | // 5 > 4, so go right 55 | └── root.right = insertIntoBST(6, 5) // At node 6 56 | // 5 < 6, so go left 57 | └── root.left = insertIntoBST(null, 5) // Hit null! 58 | // Base case: return new TreeNode(5) 59 | └── return Node(5) 60 | // 6's left pointer gets Node(5) 61 | // Returns node 6 with new left child 62 | └── 4's right pointer gets updated 6 63 | // Finally return root (4) 64 | ``` 65 | -------------------------------------------------------------------------------- /koko-eating-bananas.md: -------------------------------------------------------------------------------- 1 | # Koko Eating Bananas 2 | 3 | ```js 4 | /** 5 | * @param {number[]} piles 6 | * @param {number} h 7 | * @return {number} 8 | */ 9 | var minEatingSpeed = function (piles, h) { 10 | let left = 1; 11 | let right = Math.max(...piles); 12 | 13 | let res = right; 14 | 15 | while (left <= right) { 16 | let mid = Math.floor((left + right) / 2); 17 | 18 | let hours = 0; 19 | 20 | for (let i = 0; i < piles.length; i++) { 21 | hours += Math.ceil(piles[i] / mid); 22 | } 23 | 24 | if (hours <= h) { 25 | res = Math.min(res, mid); 26 | right = mid - 1; 27 | } else { 28 | left = mid + 1; 29 | } 30 | } 31 | 32 | return res; 33 | }; 34 | ``` 35 | 36 | this one is quite tricky till you really understand the problem 37 | 38 | the problem is that we need to find the minimum speed that koko can eat all the bananas in h hours, she eats pile per pile, but koko only eats maximum one pile per hour 39 | 40 | so we need to find the minimum speed that koko can eat all the bananas in h hours 41 | 42 | because she eats maximum one pile per hour, the highest speed is the maximum pile size 43 | 44 | 1 is lowest because she gotta eat something lol 45 | 46 | we're doing a binary search, where mid represents the speed 47 | 48 | each time we check using Math.ceil(piles[i] / mid) to get the hours it takes to eat all the bananas at the current speed, the reason we need Math.ceil is because if we hit any decimals, it should be more than 1 hour, we only care about hours here 49 | 50 | if hours is less than or equal to h, we know that we can reduce the speed, so we set right to mid - 1, and result to the minimum of result and mid, if res is still less than mid, we just stick to res being res, otherwise we update it to mid 51 | 52 | in the end we return res 53 | 54 | if that is not the case, we know that koko needs to eat more, so we set left to mid + 1, making sure to increase the speed 55 | -------------------------------------------------------------------------------- /kth-largest-element-in-a-stream.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * @param {number} k 4 | * @param {number[]} nums 5 | */ 6 | var KthLargest = function (k, nums) { 7 | this.minQueue = new MinPriorityQueue(); 8 | this.k = k; 9 | for (const num of nums) { 10 | this.add(num); 11 | } 12 | }; 13 | 14 | KthLargest.prototype.add = function (val) { 15 | this.minQueue.enqueue(val); 16 | 17 | while (this.minQueue.size() > this.k) { 18 | this.minQueue.dequeue(); 19 | } 20 | 21 | return this.minQueue.front().element; 22 | }; 23 | ``` 24 | 25 | we wanna keep track of the kth largest element 26 | 27 | this means everytime we add, we wanna return for the addition the kth largest element 28 | 29 | the best thing is to use a min priority queue 30 | 31 | it's gonna automatically sort the elements for us 32 | 33 | when u add an element, u jutk eep dequeuing until the size is k, when size is k, you return the front 34 | 35 | which is the kth largest element 36 | 37 | "kth largest" means the smallest element in the top k elements 38 | 39 | for example 40 | 41 | if k is 3 42 | 43 | we're looking for the 3rd largest element, not the largest, but the 3rd largest 44 | 45 | so if we have [1,2,3,4,5] 46 | 47 | the 3rd largest is 3, since third position 48 | 49 | an example 50 | 51 | ```js 52 | // Example: Find 3rd largest in [4,8,5,10,3] 53 | minHeap: [4]; // Add 4 54 | minHeap: [4, 8]; // Add 8 55 | minHeap: [4, 5, 8]; // Add 5 56 | minHeap: [5, 8, 10]; // Add 10, dequeue 4 (too small!) 57 | minHeap: [5, 8, 10]; // Add 3, dequeue immediately (too small!) 58 | ``` 59 | -------------------------------------------------------------------------------- /kth-largest-element-in-an-array.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * @param {number[]} nums 4 | * @param {number} k 5 | * @return {number} 6 | */ 7 | var findKthLargest = function (nums, k) { 8 | const minQueue = new MinPriorityQueue(); 9 | 10 | for (const num of nums) { 11 | minQueue.enqueue(num); 12 | } 13 | 14 | while (minQueue.size() > k) { 15 | minQueue.dequeue(); 16 | } 17 | 18 | return minQueue.front().element; 19 | }; 20 | ``` 21 | 22 | this is the same as kth largest element in a stream 23 | 24 | we wanna find kth largest 25 | 26 | by using a min queue, we can easily remove smaller numbers and keep the bigger ones 27 | 28 | which is exactly what we want 29 | 30 | we make sure size is k 31 | 32 | then we immediately have acccess to the kth largest element 33 | 34 | in this case we add all nums and then dequeue 35 | 36 | ```js 37 | // Example: Find 3rd largest in [4,8,5,10,3] 38 | minHeap: [4, 8, 5, 10, 3]; // Add all nums 39 | // we know dequeue 3 and 4 40 | // so we're left with [5, 8, 10] 41 | // so the 3rd largest is 5 42 | ``` 43 | -------------------------------------------------------------------------------- /kth-smallest-element-in-a-bst.md: -------------------------------------------------------------------------------- 1 | # Kth Smallest Element in a BST 2 | 3 | # Iterative approach 4 | 5 | ```js 6 | /** 7 | * Definition for a binary tree node. 8 | * function TreeNode(val, left, right) { 9 | * this.val = (val===undefined ? 0 : val) 10 | * this.left = (left===undefined ? null : left) 11 | * this.right = (right===undefined ? null : right) 12 | * } 13 | */ 14 | /** 15 | * @param {TreeNode} root 16 | * @param {number} k 17 | * @return {number} 18 | */ 19 | var kthSmallest = function (root, k) { 20 | let stack = []; 21 | let curr = root; 22 | let n = 0; 23 | 24 | while (curr || stack.length) { 25 | while (curr) { 26 | stack.push(curr); 27 | curr = curr.left; 28 | } 29 | 30 | curr = stack.pop(); 31 | 32 | n += 1; 33 | 34 | if (n === k) return curr.val; 35 | 36 | curr = curr.right; 37 | } 38 | }; 39 | ``` 40 | 41 | to get the kth smallest, we need to traverse the with inorder traversal, so that we can process the nodes in ascending order, from smallest to largest 42 | 43 | every time we process a node, we increment n, if its equal to k, we return the node's value 44 | 45 | the iterative approach can be tricky to understand 46 | 47 | we have a stack, curr starts at root, but tbh its not gonna be the root, we just need to be some value so that we can start the while loop, now another approach could be to have root be in stack as the initial value 48 | 49 | which is kind of what's happening in the first while loop inside the main while loop where we push curr and keep going left until we hit null 50 | 51 | the reason we keep going left is because we want to find the smallest node, so we need to go all the way left 52 | 53 | when the going left loop is done, curr will be null, that's why it ends, so we get the latest value that existed from the stack 54 | 55 | we process the node 56 | 57 | and then we wanna go right the next time 58 | 59 | if right exists, what we'll do in the next loop is push it to the stack, and then keep going left 60 | 61 | this is how inorder traversal works, that's why it's always smallest to largest 62 | 63 | if curr.right is null 64 | 65 | we skill the loop where we push curr, we just pop from the stack and assign that to curr (which we always do) 66 | 67 | this is gonna be the node that was the parent of the smallest node if the smallest node didn't have a right child 68 | 69 | --- 70 | 71 | # Recursive approach 72 | 73 | ```js 74 | /** 75 | * Definition for a binary tree node. 76 | * function TreeNode(val, left, right) { 77 | * this.val = (val===undefined ? 0 : val) 78 | * this.left = (left===undefined ? null : left) 79 | * this.right = (right===undefined ? null : right) 80 | * } 81 | */ 82 | /** 83 | * @param {TreeNode} root 84 | * @param {number} k 85 | * @return {number} 86 | */ 87 | var kthSmallest = function (root, k, n = { count: 0 }) { 88 | if (!root) return null; 89 | 90 | const left = kthSmallest(root.left, k, n); 91 | 92 | if (left !== null) return left; 93 | 94 | n.count += 1; 95 | 96 | if (n.count === k) return root.val; 97 | 98 | return kthSmallest(root.right, k, n); 99 | }; 100 | ``` 101 | 102 | the recursive approach is really interesting 103 | 104 | its easy if u understand recursion 105 | 106 | if u dont, its gonna be hard 107 | 108 | lets go through what happens here 109 | 110 | first thing youll see we have count, we need to use an object here since we gotta keep track of the reference and modify it throughout the function calls 111 | -------------------------------------------------------------------------------- /last-stone-weight.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * @param {number[]} stones 4 | * @return {number} 5 | */ 6 | var lastStoneWeight = function (stones) { 7 | const maxPriorityQueue = new MaxPriorityQueue(); 8 | 9 | for (const stone of stones) { 10 | maxPriorityQueue.enqueue(stone); 11 | } 12 | 13 | while (maxPriorityQueue.size() > 1) { 14 | const firstStone = maxPriorityQueue.dequeue().element; 15 | const secondStone = maxPriorityQueue.dequeue().element; 16 | 17 | if (firstStone !== secondStone) { 18 | const result = firstStone - secondStone; 19 | maxPriorityQueue.enqueue(result); 20 | } 21 | } 22 | 23 | return maxPriorityQueue.isEmpty() ? 0 : maxPriorityQueue.front().element; 24 | }; 25 | ``` 26 | 27 | we wanna take thw two largest stones and smash them together 28 | 29 | if they're not the same, we add the difference back to the queue 30 | 31 | largest is the key here, max heap is perfect for this 32 | 33 | we continue till we have one or no stones left 34 | 35 | then we return the last stone or 0 if there are no stones left 36 | 37 | ```js 38 | // Example: Last stone weight in [2,7,4,1,8,1] 39 | // maxHeap: [8,7,4,2,1,1]; // Add all stones 40 | // maxHeap: [4,2,1,1]; // Smash 8 and 7, add 1 41 | // maxHeap: [2,1,1]; // Smash 4 and 2, add 2 42 | // maxHeap: [1,1]; // Smash 2 and 1, add 1 43 | // maxHeap: [1]; // Smash 1 and 1, no stones left 44 | // Result: 1 45 | ``` 46 | -------------------------------------------------------------------------------- /lru-cache.md: -------------------------------------------------------------------------------- 1 | ```js 2 | class Node { 3 | constructor(key = null, val = null, prev = null, next = null) { 4 | this.key = key; 5 | this.val = val; 6 | this.prev = prev; 7 | this.next = next; 8 | } 9 | } 10 | 11 | /** 12 | * @param {number} capacity 13 | */ 14 | var LRUCache = function (capacity) { 15 | this.capacity = capacity; 16 | this.length = 0; 17 | this.cache = {}; 18 | 19 | this.left = new Node(); 20 | this.right = new Node(); 21 | 22 | this.left.next = this.right; 23 | this.right.prev = this.left; 24 | }; 25 | 26 | LRUCache.prototype.insert = function (node) { 27 | let prevRight = this.right.prev; 28 | this.right.prev = node; 29 | node.next = this.right; 30 | node.prev = prevRight; 31 | 32 | prevRight.next = node; 33 | }; 34 | 35 | LRUCache.prototype.remove = function (node) { 36 | let prev = node.prev; 37 | let next = node.next; 38 | 39 | prev.next = next; 40 | next.prev = prev; 41 | }; 42 | 43 | /** 44 | * @param {number} key 45 | * @return {number} 46 | */ 47 | LRUCache.prototype.get = function (key) { 48 | let node = this.cache[key]; 49 | if (node !== undefined) { 50 | this.remove(node); 51 | this.insert(node); 52 | return node.val; 53 | } 54 | 55 | return -1; 56 | }; 57 | 58 | /** 59 | * @param {number} key 60 | * @param {number} value 61 | * @return {void} 62 | */ 63 | LRUCache.prototype.put = function (key, value) { 64 | if (this.cache[key] !== undefined) { 65 | let node = this.cache[key]; 66 | this.remove(node); 67 | this.insert(node); 68 | node.val = value; 69 | } else { 70 | if (this.length === this.capacity) { 71 | let lru = this.left.next; 72 | this.remove(lru); 73 | delete this.cache[lru.key]; 74 | this.length--; 75 | } 76 | 77 | let newNode = new Node(key, value); 78 | this.insert(newNode); 79 | this.cache[key] = newNode; 80 | this.length++; 81 | } 82 | }; 83 | 84 | /** 85 | * Your LRUCache object will be instantiated and called as such: 86 | * var obj = new LRUCache(capacity) 87 | * var param_1 = obj.get(key) 88 | * obj.put(key,value) 89 | */ 90 | ``` 91 | 92 | the trick here is to have both the cache for o of 1 lookup and the linked list for o of 1 insertion and deletion 93 | 94 | doubly linked list is there to keep track of the order of the nodes 95 | 96 | the good part is we only need to know the least and most recent node, so we can remove and insert in o of 1 time 97 | 98 | the ones in the middle we dont need to knwo their order 99 | 100 | the back one is the important one due to eviction 101 | 102 | we have a remove and insert helper since theyre used in multiple places 103 | 104 | the key here is understanding that getting and updating also causes the node to move to the front 105 | 106 | that is why we have the remove and insert helpers, theyre useful and used a lot 107 | 108 | its not too hard if u wrap your head around the entire thing 109 | -------------------------------------------------------------------------------- /max-area-of-island.md: -------------------------------------------------------------------------------- 1 | # Max Area of Island 2 | 3 | ```js 4 | /** 5 | * @param {number[][]} grid 6 | * @return {number} 7 | */ 8 | var maxAreaOfIsland = function (grid) { 9 | let ROWS = grid.length; 10 | let COLUMNS = grid[0].length; 11 | let maxArea = 0; 12 | 13 | for (let row = 0; row < ROWS; row++) { 14 | for (let col = 0; col < COLUMNS; col++) { 15 | if (grid[row][col] === 1) { 16 | let newArea = floodFill(grid, row, col); 17 | maxArea = Math.max(newArea, maxArea); 18 | } 19 | } 20 | } 21 | 22 | return maxArea; 23 | }; 24 | 25 | function floodFill(grid, row, col) { 26 | if (Math.min(row, col) < 0) return 0; 27 | if (row === grid.length || col === grid[0].length) return 0; 28 | if (grid[row][col] === 2 || grid[row][col] === 0) return 0; 29 | 30 | let count = 1; 31 | grid[row][col] = 2; 32 | 33 | count += floodFill(grid, row + 1, col); 34 | count += floodFill(grid, row - 1, col); 35 | count += floodFill(grid, row, col - 1); 36 | count += floodFill(grid, row, col + 1); 37 | 38 | return count; 39 | } 40 | ``` 41 | 42 | this is the same as number of islands 43 | 44 | the difference here though is that we wanna count the area of the island 45 | 46 | everytime we get the result, we update it with the max of our current result and the new result 47 | 48 | inside the flood fill, we update the grid in place to mark the islands as visited 49 | 50 | count starts at 1 because we are counting the current cell 51 | -------------------------------------------------------------------------------- /merge-two-sorted-lists.md: -------------------------------------------------------------------------------- 1 | # Merging Two Sorted Lists 2 | 3 | ```js 4 | /** 5 | * Definition for singly-linked list. 6 | * function ListNode(val, next) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.next = (next===undefined ? null : next) 9 | * } 10 | */ 11 | /** 12 | * @param {ListNode} list1 13 | * @param {ListNode} list2 14 | * @return {ListNode} 15 | */ 16 | var mergeTwoLists = function (list1, list2) { 17 | let dummy = new ListNode(); 18 | let tail = dummy; 19 | 20 | // only while both lists have nodes 21 | // a list is a node with a value and a pointer to the next node 22 | while (list1 && list2) { 23 | // if list1 is greater than list2, we want to add list2 to the tail 24 | if (list1.val > list2.val) { 25 | // new node for the merged list 26 | tail.next = list2; 27 | // move list2 pointer forward 28 | list2 = list2.next; 29 | } else { 30 | // new node for the merged list 31 | tail.next = list1; 32 | // move list1 pointer forward 33 | list1 = list1.next; 34 | } 35 | 36 | // this is safe to do 37 | // we're just moving the tail pointer forward to the node we just added 38 | tail = tail.next; 39 | } 40 | 41 | // if list1 has nodes left 42 | // means exited loop because list2 is null 43 | if (list1) { 44 | tail.next = list1; 45 | } 46 | 47 | // if list2 has nodes left 48 | // means exited loop because list1 is null 49 | if (list2) { 50 | tail.next = list2; 51 | } 52 | 53 | return dummy.next; 54 | }; 55 | ``` 56 | 57 | this one is actually easy for the most part 58 | 59 | the tricky part here is how on earth does dummy.next return the head? 60 | 61 | let's focus on this for a while 62 | 63 | when you do tail = dummy, you're telling tail to point to the same memory address as dummy 64 | 65 | that's why when you do tail.next, you are actually modifying the dummy.next, they share the same memory address 66 | 67 | ```js 68 | let dummy = new ListNode(); // Create a new node in memory, let's say at address X 69 | let tail = dummy; // tail now points to the SAME memory address X 70 | ``` 71 | 72 | ```js 73 | // Let's say we're adding value 2 74 | tail.next = list2; // This modifies the "next" pointer at memory address X 75 | tail = tail.next; // tail now points to a new memory address (list2's address) 76 | ``` 77 | 78 | what happens on the surface is you telling tail and dummy what to point to, under the hood is where the real stuff is stored 79 | 80 | --- 81 | -------------------------------------------------------------------------------- /number-of-islands.md: -------------------------------------------------------------------------------- 1 | # Number of Islands 2 | 3 | ```js 4 | /** 5 | * @param {character[][]} grid 6 | * @return {number} 7 | */ 8 | var numIslands = function (grid) { 9 | let ROWS = grid.length; 10 | let COLUMNS = grid[0].length; 11 | let count = 0; 12 | 13 | for (let row = 0; row < ROWS; row++) { 14 | for (let col = 0; col < COLUMNS; col++) { 15 | if (grid[row][col] === "1") { 16 | count++; 17 | floodFill(grid, row, col); 18 | } 19 | } 20 | } 21 | 22 | return count; 23 | }; 24 | 25 | function floodFill(grid, row, col) { 26 | if (Math.min(row, col) < 0) return; 27 | if (row === grid.length || col === grid[0].length) return; 28 | if (grid[row][col] === "0" || grid[row][col] === "2") return; 29 | 30 | grid[row][col] = "2"; 31 | 32 | floodFill(grid, row + 1, col); 33 | floodFill(grid, row - 1, col); 34 | floodFill(grid, row, col - 1); 35 | floodFill(grid, row, col + 1); 36 | } 37 | ``` 38 | 39 | islands are 1s, only valid of horizontally or vertically connected 40 | 41 | this works by updating it in place 42 | 43 | the flood fill is a dfs that updates the grid in place to mark the islands as visited 44 | 45 | everytime we find a 1, we know its connected to an island, so we can just mark it as visited and call flood fill on the 4 directions to see how it expands 46 | -------------------------------------------------------------------------------- /path-sum.md: -------------------------------------------------------------------------------- 1 | # Path Sum 2 | 3 | ```js 4 | /** 5 | * Definition for a binary tree node. 6 | * function TreeNode(val, left, right) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.left = (left===undefined ? null : left) 9 | * this.right = (right===undefined ? null : right) 10 | * } 11 | */ 12 | /** 13 | * @param {TreeNode} root 14 | * @param {number} targetSum 15 | * @return {boolean} 16 | */ 17 | var hasPathSum = function (root, targetSum, totalSum = 0) { 18 | if (!root) return false; 19 | 20 | totalSum += root.val; 21 | 22 | if (!root.left && !root.right) { 23 | if (targetSum === totalSum) { 24 | return true; 25 | } 26 | } 27 | 28 | if (hasPathSum(root.left, targetSum, totalSum)) return true; 29 | if (hasPathSum(root.right, targetSum, totalSum)) return true; 30 | 31 | return false; 32 | }; 33 | ``` 34 | 35 | here we use backtracking 36 | 37 | its interesting tho because we only need to pass by value of totalSum 38 | 39 | thats why we dont have to reduce it before returning false back up in the end 40 | 41 | the goal is to find a path to leaf node that matches the targetSum 42 | 43 | if a node exists, we process it, add it to totalSum 44 | 45 | if its a leaf, we check if the totalSum matches the targetSum, if so, return true 46 | 47 | if not 48 | 49 | we wanna continue down the left path first, if it returns true, we return true, we know it'd only return true if it's a leaf and the totalSum matches the targetSum 50 | 51 | if not, we try right 52 | 53 | in the end, if we didnt find anything for this subtree, we return false 54 | 55 | you can imagine hasPathSum with root.left waiting 56 | 57 | and then it'd get false back up 58 | 59 | then it knows ah ok, we'll try the right path, nothing in this subtree worked 60 | 61 | ``` 62 | 4 63 | / \ 64 | 2 6 65 | / \ 66 | 1 0 67 | ``` 68 | 69 | ```js 70 | hasPathSum(4, 6, 0); 71 | ``` 72 | 73 | we keep going down the left path first 74 | 75 | ```js 76 | hasPathSum(2, 6, 4); 77 | ``` 78 | 79 | after processing 2, we go down the left path again 80 | 81 | ```js 82 | hasPathSum(1, 6, 6); 83 | ``` 84 | 85 | in here, we see that 1 is a leaf, so we check if the totalSum matches the targetSum 86 | 87 | ```js 88 | 6 === 6; 89 | ``` 90 | 91 | so we return true 92 | 93 | if we were looking for 7, it would return false, and it'd return false all the way back up to 4 94 | 95 | then we check 4.right, which is 6 96 | 97 | ```js 98 | hasPathSum(6, 7, 4); 99 | ``` 100 | 101 | ```js 102 | hasPathSum(0, 7, 10); 103 | ``` 104 | 105 | here we see that 0 is a leaf, so we check if the totalSum matches the targetSum, it doesnt, we're at 10, so we return false 106 | 107 | now before we return false, we'd check left and right of 0, they both return false 108 | 109 | therefore we continue and return false all the way back up to 4 110 | -------------------------------------------------------------------------------- /reverse-linked-list.md: -------------------------------------------------------------------------------- 1 | # Reverse Linked List 2 | 3 | # Iterative approach 4 | 5 | ```js 6 | /** 7 | * Definition for singly-linked list. 8 | * function ListNode(val, next) { 9 | * this.val = (val===undefined ? 0 : val) 10 | * this.next = (next===undefined ? null : next) 11 | * } 12 | */ 13 | /** 14 | * @param {ListNode} head 15 | * @return {ListNode} 16 | */ 17 | var reverseList = function (head) { 18 | let prev = null; 19 | let curr = head; 20 | 21 | while (curr) { 22 | // Store next node 23 | let next = curr.next; 24 | 25 | // Reverse the link 26 | // This is the key part of the algorithm 27 | curr.next = prev; 28 | 29 | // Move pointers forward 30 | prev = curr; 31 | curr = next; 32 | } 33 | 34 | // prev will be the new head 35 | // Curr after loop will be null 36 | return prev; 37 | }; 38 | ``` 39 | 40 | Here we create prev and curr pointers. We know that prev the first time is null because head doesn't have a previous node. But head will become the tail and we know that's gonna point to null. 41 | 42 | We can keep going through the list till we reach the end. In the final iteration, next that curr gets assigned to will be null. Curr will be null. Prev will however point to the new head, which previously was the last node, the tail. 43 | 44 | We need to store next in a temporary variable because we overwrite curr.next with prev. Otherwise we'd lose the reference to the next node. 45 | 46 | # Recursive approach 47 | 48 | ```js 49 | /** 50 | * Definition for singly-linked list. 51 | * function ListNode(val, next) { 52 | * this.val = (val===undefined ? 0 : val) 53 | * this.next = (next===undefined ? null : next) 54 | * } 55 | */ 56 | /** 57 | * @param {ListNode} head 58 | * @return {ListNode} 59 | */ 60 | var reverseList = function (head) { 61 | function reverse(node) { 62 | if (!node || !node.next) return node; 63 | 64 | const reversed = reverse(node.next); 65 | 66 | node.next.next = node; 67 | node.next = null; 68 | 69 | return reversed; 70 | } 71 | 72 | return reverse(head); 73 | }; 74 | ``` 75 | 76 | This is the recursive approach. It looks confusing, but let's break it down with a clear example. 77 | 78 | 1 -> 2 -> 3 -> 4 -> 5 79 | 80 | We'll keep calling reverse on the next node until we reach the final node. When we reach the final node, node.next is null, therefore we return it BACK up. 81 | 82 | Back up is important here. When we return it back up, we'll be at 4's level, where node is 4 and reversed is 5. This is key to understanding the algorithm. Otherwise it's very confusing how node.next.next is not null. 83 | 84 | node.next.next would the first time be 4.next.next = 4. Which is 4.5.next = 4. 5 is the tail. We tell 5 to point to 4, which came before 5. That's how we reverse the list. 85 | 86 | node.next to null is just to break the link. Since 3 will do exactly the same thing. We're modifying this in place. Objects are passed by reference, so don't get that confused. 87 | 88 | Finally, we return reversed. In the level of 3, we're waiting for `reverse(4)`. Because we're modifying in place, it's gonna return 5->4->null. 89 | 90 | The beauty with recursion is that once we're at the end, we just keep going back up. 91 | -------------------------------------------------------------------------------- /search-a-2d-matrix.md: -------------------------------------------------------------------------------- 1 | # Search a 2D Matrix 2 | 3 | ```js 4 | /** 5 | * @param {number[][]} matrix 6 | * @param {number} target 7 | * @return {boolean} 8 | */ 9 | var searchMatrix = function (matrix, target) { 10 | const COLUMNS = matrix[0].length; 11 | const ROWS = matrix.length; 12 | 13 | let top = 0; 14 | let bottom = ROWS - 1; 15 | 16 | let targetRow = null; 17 | 18 | while (top <= bottom) { 19 | let mid = Math.floor((top + bottom) / 2); 20 | let row = matrix[mid]; 21 | 22 | let firstVal = row[0]; 23 | let lastVal = row[COLUMNS - 1]; 24 | 25 | if (target >= firstVal && target <= lastVal) { 26 | targetRow = row; 27 | break; 28 | } else if (target > lastVal) { 29 | top = mid + 1; 30 | } else { 31 | bottom = mid - 1; 32 | } 33 | } 34 | 35 | if (targetRow === null) return false; 36 | 37 | let L = 0; 38 | let R = targetRow.length - 1; 39 | 40 | while (L <= R) { 41 | let mid = Math.floor((L + R) / 2); 42 | 43 | if (target > targetRow[mid]) { 44 | L = mid + 1; 45 | } else if (target < targetRow[mid]) { 46 | R = mid - 1; 47 | } else { 48 | return true; 49 | } 50 | } 51 | 52 | return false; 53 | }; 54 | ``` 55 | 56 | the latter part is just a normal binary search 57 | 58 | well the first part too, to an extent, it may be confusing 59 | 60 | but essentially we have rows and columns 61 | 62 | ```js 63 | const COLUMNS = matrix[0].length; 64 | const ROWS = matrix.length; 65 | 66 | let top = 0; 67 | let bottom = ROWS - 1; 68 | 69 | let targetRow = null; 70 | 71 | while (top <= bottom) { 72 | let mid = Math.floor((top + bottom) / 2); 73 | let row = matrix[mid]; 74 | 75 | let firstVal = row[0]; 76 | let lastVal = row[COLUMNS - 1]; 77 | 78 | if (target >= firstVal && target <= lastVal) { 79 | targetRow = row; 80 | break; 81 | } else if (target > lastVal) { 82 | top = mid + 1; 83 | } else { 84 | bottom = mid - 1; 85 | } 86 | } 87 | ``` 88 | 89 | we need to do a binary search top down to find the row that contains the target 90 | 91 | to find if the target is in the row, we check if the target is greater than or equal to the first value and less than or equal to the last value of the row 92 | 93 | equal is fine because it could be the target, so that's totally cool 94 | 95 | otherwise if the target is greater than the last value, we know it's not in that row, so we move the top down 96 | 97 | or the else case, which we know for a fact gotta be less than the first value, so we move the bottom up 98 | -------------------------------------------------------------------------------- /search-in-a-binary-search-tree.md: -------------------------------------------------------------------------------- 1 | # Search a BST 2 | 3 | ```js 4 | /** 5 | * Definition for a binary tree node. 6 | * function TreeNode(val, left, right) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.left = (left===undefined ? null : left) 9 | * this.right = (right===undefined ? null : right) 10 | * } 11 | */ 12 | 13 | /** 14 | * @param {TreeNode} root 15 | * @param {number} val 16 | * @return {TreeNode} 17 | */ 18 | var searchBST = function (root, val) { 19 | if (!root) return root; 20 | 21 | if (val > root.val) { 22 | return searchBST(root.right, val); 23 | } else if (val < root.val) { 24 | return searchBST(root.left, val); 25 | } else { 26 | return root; 27 | } 28 | }; 29 | ``` 30 | 31 | this gotta be one of my favorite stuff 32 | 33 | i love recursion and trees 34 | 35 | now i wanna be clear about the base case if you were thinking about it 36 | 37 | yes, we could just return null there, root would be null since root.right or root.left which we called with would be null 38 | 39 | --- 40 | 41 | lets go over an example, since recursion is such a beautiful thing 42 | 43 | ``` 44 | 4 45 | / \ 46 | 2 7 47 | / \ / \ 48 | 1 3 6 8 49 | ``` 50 | 51 | if we were looking for 7: 52 | 53 | ```js 54 | searchBST(4, 7) // Start at root (4) 55 | // 7 > 4, so go right 56 | └── return searchBST(7, 7) // At node 7 57 | // 7 === 7, so return this node 58 | └── return node 7 and its subtree (7->6,8) 59 | // Parent (4) gets this result and returns it up 60 | ``` 61 | 62 | the interesting thing here is that if we dont find the value 63 | 64 | we immediatly return 65 | 66 | this means when we bubble up 67 | 68 | each return is just gonna be the one we returned from the bottom, which is either null or the node we found 69 | 70 | --- 71 | 72 | another example, looking for 9 which doesnt exist: 73 | 74 | ```js 75 | searchBST(4, 9) // Start at root (4) 76 | // 9 > 4, so go right 77 | └── return searchBST(7, 9) // At node 7 78 | // 9 > 7, so go right 79 | └── return searchBST(8, 9) // At node 8 80 | // 9 > 8, so go right 81 | └── return searchBST(null, 9) // Hit null! 82 | // Base case: return null 83 | └── return null 84 | // Parent (8) returns null 85 | // Parent (7) returns null 86 | // Parent (4) returns null 87 | ``` 88 | 89 | here we just keep going right until we hit null and return null 90 | 91 | becaue we're always returning everytime we go down the chain, every value will return null back up the chain 92 | -------------------------------------------------------------------------------- /shortest-path-in-binary-matrix.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * @param {number[][]} grid 4 | * @return {number} 5 | */ 6 | var shortestPathBinaryMatrix = function (grid) { 7 | if (grid[0][0] === 1) return -1; 8 | 9 | let ROWS = grid.length; 10 | let COLUMNS = grid[0].length; 11 | let queue = [[0, 0]]; 12 | let visited = new Set(`0,0`); 13 | let length = 1; 14 | 15 | while (queue.length) { 16 | let lenSnapshot = queue.length; 17 | 18 | for (let i = 0; i < lenSnapshot; i++) { 19 | const [row, col] = queue.shift(); 20 | 21 | if (row === ROWS - 1 && col === COLUMNS - 1) return length; 22 | 23 | const neighbors = [ 24 | [-1, 0], 25 | [1, 0], 26 | [0, -1], 27 | [0, 1], 28 | [1, 1], 29 | [-1, -1], 30 | [1, -1], 31 | [-1, 1], 32 | ]; 33 | 34 | for (const [rowDir, colDir] of neighbors) { 35 | const newRow = row + rowDir; 36 | const newCol = col + colDir; 37 | const key = `${newRow},${newCol}`; 38 | 39 | if (Math.min(newRow, newCol) < 0) continue; 40 | if (newRow === ROWS || newCol === COLUMNS) continue; 41 | if (grid[newRow][newCol] === 1 || visited.has(key)) continue; 42 | 43 | visited.add(key); 44 | queue.push([newRow, newCol]); 45 | } 46 | } 47 | 48 | length++; 49 | } 50 | 51 | return -1; 52 | }; 53 | ``` 54 | 55 | The idea here is BFS to find the shortest path in a binary matrix. This includes not just going horizontally or vertically, but also diagonally. That's why neighbors as you see, are 8 in total. 56 | 57 | For every row and column, we first check if we found our target, if not, we then explore all neighbors. 58 | 59 | We add all valid neighbors to the queue and visited set. When done exploring all neighbors, we move onto the next iteration, before doing so, we increment length. 60 | 61 | We loop through the next level and follow the same process. 62 | -------------------------------------------------------------------------------- /subsets.md: -------------------------------------------------------------------------------- 1 | # Subsets 2 | 3 | ```js 4 | /** 5 | * @param {number[]} nums 6 | * @return {number[][]} 7 | */ 8 | var subsets = function (nums) { 9 | let res = []; 10 | let subset = []; 11 | 12 | function dfs(i) { 13 | if (i >= nums.length) { 14 | res.push(subset.slice()); 15 | return; 16 | } 17 | 18 | subset.push(nums[i]); 19 | dfs(i + 1); 20 | 21 | subset.pop(); 22 | dfs(i + 1); 23 | } 24 | 25 | dfs(0); 26 | return res; 27 | }; 28 | ``` 29 | 30 | this one is really tricky 31 | 32 | we wanna know all the possible subsets of an array 33 | 34 | before we go over the entire thing 35 | 36 | i think it'd be great to understand two things 37 | 38 | the first one i that we need to do .slice because we're modifying the subset array throughout the dfs function calls, .slice copies the subset array with a new reference, this way we don't modify what we pushed to the res array 39 | 40 | the second one is that we're going down on path at a time, we don't do things concurrently 41 | 42 | two examples help clarify this 43 | 44 | lets look at the first one 45 | 46 | ```js 47 | // What we need: 48 | res.push(subset.slice()) // RIGHT! Pushes a new copy 49 | 50 | // Example with [1,2]: 51 | dfs(0): 52 | subset = [1] 53 | dfs(1): 54 | subset = [1,2] 55 | res.push(subset.slice()) // Pushes new [1,2] array 56 | subset.pop() // Back to [1] 57 | res.push(subset.slice()) // Pushes new [1] array 58 | subset.pop() // Back to [] 59 | // And so on... 60 | ``` 61 | 62 | here we keep adding to the subset array as we go down till index is greater than or equal to nums.length, which means there are no more nums to access 63 | 64 | we add to the res array a new copy of the subset array 65 | 66 | then we pop the last element from the subset array 67 | 68 | when we do it again, because in the newcall i will be greater than or equal to nums.length, we push a new copy of the subset array to the res array, which this time will be without the last element 69 | 70 | so we called dfs 0, we added 1 to the subset arr 71 | 72 | then we called dfs 1, we added 2 to the subset arr 73 | 74 | we then called dfs 2, this is the lenght of arr, so we push a new copy of the subset arr to the res array, then return 75 | 76 | return just returns undefined, but this isnt relevant, the point here is that we're stopping the recursion and going back up to continue the function in the parent 77 | 78 | we popped the 2 which was at index 1 79 | 80 | we then call dfs 2 again, this time we popped, so only [1] remains in the subset arr, and we still call it with the same index like the first time we icnremented 81 | 82 | this is because we wanna know the variations of the subsets 83 | 84 | --- 85 | 86 | ```js 87 | let nums = [1,2,3,4]; 88 | let subset = []; 89 | 90 | // First deep dive including everything: 91 | dfs(0): // First call with 1 92 | subset.push(1) // subset = [1] 93 | dfs(1) // Dive deeper with 2 94 | subset.push(2) // subset = [1,2] 95 | dfs(2) // Dive deeper with 3 96 | subset.push(3) // subset = [1,2,3] 97 | dfs(3) // Dive deeper with 4 98 | subset.push(4) // subset = [1,2,3,4] 99 | dfs(4) // i >= length! 100 | // Push copy of [1,2,3,4] to result 101 | return 102 | subset.pop() // Remove 4, subset = [1,2,3] 103 | dfs(4) // Try without 4 104 | // Push copy of [1,2,3] to result 105 | return 106 | subset.pop() // Remove 3, subset = [1,2] 107 | // Now we'll try paths without 3... 108 | ``` 109 | 110 | this is a bigger example, which clearly demonstrates the dfs function going deep into the first path 111 | -------------------------------------------------------------------------------- /two-sum.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * @param {number[]} nums 4 | * @param {number} target 5 | * @return {number[]} 6 | */ 7 | var twoSum = function (nums, target) { 8 | let hash = {}; 9 | 10 | for (let i = 0; i < nums.length; i++) { 11 | let diff = target - nums[i]; 12 | 13 | if (hash[diff] !== undefined) { 14 | return [hash[diff], i]; 15 | } 16 | 17 | hash[nums[i]] = i; 18 | } 19 | }; 20 | ``` 21 | 22 | the trick to this one is using a hash map 23 | 24 | we store all the numbers and map them to their index 25 | 26 | we wanna return an array of the two indices that add up to the target 27 | 28 | by subtracting the target from the current number, we get the difference, the difference added with nums[i] should equal the target 29 | 30 | thats how we know if it exists in the hash map, then we return the index of the difference and the current index 31 | --------------------------------------------------------------------------------