LeetCode
876. Middle of the Linked List Given a non-empty, singly linked list with head node head, return a middle node of linked list. If there are two middle nodes, return the second middle node. Note: The number of nodes in the given list will be between 1 and 100.
Insight/Solution: slow and fast pointers
48. Rotate Image You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise). You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation. Constraints: matrix.length == n matrix[i].length == n 1 <= n <= 20 -1000 <= matrix[i][j] <= 1000
Insight/Solution: Arrays + Linear Algebra (Transpose matrix) transpose the matrix, then reverse each row i.e. 1 2 3 4 5 6 7 8 9 apply transpose 1 4 7 2 5 8 3 6 9 apply reverse 7 4 1 8 5 2 9 6 3
637. Average of Levels in Binary Tree Given a non-empty binary tree, return the average value of the nodes on each level in the form of an array. Note: The range of node's value is in the range of 32-bit signed integer.
Insight/Solution: BFS
216. Combination Sum III Find all valid combinations of k numbers that sum up to n such that the following conditions are true: Only numbers 1 through 9 are used. Each number is used at most once. Return a list of all possible valid combinations. The list must not contain the same combination twice, and the combinations may be returned in any order. Constraints: 2 <= k <= 9 1 <= n <= 60
Insight/Solution: Backtracking really similar to Combination Sum but we don't necessarily have a list of numbers to work with, that's honestly the only difference
74. Search a 2D Matrix Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: Integers in each row are sorted from left to right. The first integer of each row is greater than the last integer of the previous row. Constraints: m == matrix.length n == matrix[i].length 1 <= m, n <= 100 -104 <= matrix[i][j], target <= 104
Insight/Solution: Binary Search there are two solutions using binary search: 1 with just a single binary search, the other with two binary searches both yield a complexity of O(lg(mn)) = O(lg(m) + lg(n)) using 1 binary search: treat the entire matrix as 1 sorted list by creating pivots, so r will be set to m * n - 1, and thus the pivot replaces the middle index, but the pivot is supposed to act like the middle index the pivot yields which number in the long sorted list to look at, and in terms of the matrix location, it will be stored in [pivot // n][pivot % n], now do your standard binary search using 2 binary searches: first find the row that can contain the target, which means that the target falls in between the row[0] and row[-1] elements once you find such a row, conduct a binary search on the row, your standard 1D binary search
78. Subsets Given an integer array nums, return all possible subsets (the power set). The solution set must not contain duplicate subsets. Constraints: 1 <= nums.length <= 10 -10 <= nums[i] <= 10
Insight/Solution: DFS/Backtracking, we essentially iterate through nums, update our path, and indicate where we should start with respect to the location we are currently on (start = i + 1, when we are at position i)
62. Unique Paths A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below). How many possible unique paths are there? Constraints: 1 <= m, n <= 100 It's guaranteed that the answer will be less than or equal to 2 * 109.
Insight/Solution: DP problem, here is the recursive expression: 1, if m == 1 or n == 1 f(m - 1, n) + f(m, n - 1), otherwise essentially there can only be one path if m is 1 or n is 1, since that means we can only go in that one direction
208. Implement a Trie (Prefix Tree) Implement a trie with insert, search, and startsWith methods. Note: You may assume that all inputs are consist of lowercase letters a-z. All inputs are guaranteed to be non-empty strings.
Insight/Solution: Design, just implement a trie insert: O(m) search: O(m) startsWith: O(m) m = length of word Real-life applications of a Trie: autocomplete, spell checker, IP routing (longest prefix matching), solving word games, T9 predictive text start by building the TrieNode class which will have a hash table and a boolean variable then simply build the Trie
115. Distinct Subsequences Given two strings s and t, return the number of distinct subsequences of s which equals t. A string's subsequence is a new string formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (i.e., "ACE" is a subsequence of "ABCDE" while "AEC" is not). It's guaranteed the answer fits on a 32-bit signed integer. Constraints: 0 <= s.length, t.length <= 1000 s and t consist of English letters.
Insight/Solution: Dynamic Programming
303. Range Sum Query - Immutable Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive. Implement the NumArray class: NumArray(int[] nums) Initializes the object with the integer array nums. int sumRange(int i, int j) Return the sum of the elements of the nums array in the range [i, j] inclusive (i.e., sum(nums[i], nums[i + 1], ... , nums[j])) Constraints: 0 <= nums.length <= 104 -105 <= nums[i] <= 105 0 <= i <= j < nums.length At most 104 calls will be made to sumRange.
Insight/Solution: Dynamic Programming + Math we want to cache the total sums as we iterate through the nums when he initially declare the object these sums will be very useful thanks to simple math suppose we have nums [a,b,c,d,e], and we want the range_sum(c, e) (c and e are both inclusive), then we need to find c + d + e solving this would take O(n) time consistently, which is not what we want, instead, we can use this expression: c + d + e = a + b + c + d + e - (a + b) = a + b + c + d + e - a - b = c + d + e we can now compute any range sum we want with these total sums, there is an edge case though with range_sum(0, x) where x is any integer >= 0 since range_sum is inclusive, we can set our dp table with size nums.length + 1 and set dp[0] = 0, that way, we maintain the expression property for all cases
155. Min Stack Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. push(x) -- Push element x onto stack. pop() -- Removes the element on top of the stack. top() -- Get the top element. getMin() -- Retrieve the minimum element in the stack.
Insight/Solution: Either use one stack with tuples, or two stacks Tuples: (x, min_value of stack) Two Stacks: look into it and update this flashcard
143. Reorder List Given a singly linked list L: L0→L1→...→Ln-1→Ln, reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→... You may not modify the values in the list's nodes, only nodes itself may be changed.
Insight/Solution: Fast and slow pointers this problem is a combination of 3 easy problems: Middle of the Linked List, Reverse a Linked List, and Merge 2 Linked Lists notice that the reordering we need is a merged linked list, where the latter linked list is in reverse order from the end we can assume, and even form examples, such that we recognize that we only have to reverse the right half of the linked list and merge it with the left half therefore, we find the middle of the linked list to cut our linked list into two linked lists, reverse the right half, then merge it with the left linked list
451. Sort Characters By Frequency Given a string, sort it in decreasing order based on the frequency of characters.
Insight/Solution: Hash Table + Heap, Bucket Sort idea is to use a hash table to keep count of the frequencies for each letter, then sort in reverse order by frequency, then build a string, O(n + klgk) instead of sorting, we can actually use a heap to build a string without sorting, this slightly optimizes the algorithm, O(n + klgk) we can even have just O(n) using bucket sort, the idea is to use buckets that has a size of max_freq + 1, use each index as a bucket of letters for that frequency (the index), store each bucket with letters, and we can iterate the buckets in reverse to build our string that way, we already determine what the ordering of our characters should be in linear time
136. Single Number Given a non-empty array of integers nums, every element appears twice except for one. Find that single one. Follow up: Could you implement a solution with a linear runtime complexity and without using extra memory?
Insight/Solution: Hash table or set to store numbers already seen, initialize a count = 0 and add if we haven't seen the number yet, subtract if we have, leftover of count will be our answer Follow-up: Bit manipulation, use the XOR operator i.e. [4,1,2,1,2] -> 4 ^ 1 ^ 2 ^ 1 ^ 2 -> 4 ^ (1 ^ 1) ^ (2 ^ 2) -> 4 ^ 0 ^ 0 -> 4
378. Kth Smallest Element in a Sorted Matrix Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth smallest element in the matrix. Note that it is the kth smallest element in the sorted order, not the kth distinct element. Note:You may assume k is always valid, 1 ≤ k ≤ n2.
Insight/Solution: Heap Brute force would be flattening the entire matrix into an array, sort it, then linearly search O(n^2 lg n^2) we can also flatten the matrix into an array and use a heap to push/pop until we reach the kth smallest element, which would be O(n^2 lg k) we can, again, flatten the matrix into an array and conduct quick select, which should yield O(n^2) so it seems like flattening the matrix spikes our complexity to O(n^2) at the minimum, so we should consider taking advantage of what's given to find a different algorithm without flattening the matrix, and the key is that each row and each column are sorted, the problem degenerates into finding the kth smallest element in n lists (for starters, solve for the kth smallest element in 2 lists, we use two pointers) the idea is that we could use a heap to store each list as a node, where the key is the matrix value of the list, we also want to keep track of the row and col for each matrix, so each heap node stores a triplet info (matrix[row][col], row, col) for starters, we initialize the heap with either the first row values or first column values (both work!), then continuously replace our minimum with the next element, this is simply a push and pop mechanism we can implement (or use heapreplace for python's heapq), we can keep track of which row and col to look for with the popped triplet information, if we are going by rows, we should heappush (matrix[row + 1][col], row + 1, col), otherwise (matrix[row][col + 1], row, col + 1) one last thing to consider: suppose we need to search for the 5th smallest element in a 100 x 100 matrix, when we initialize our heap, we should not need to consider the 6th row and beyond, since that would already be outside of the boundary where the 5th smallest element could be, so when we initialize our heap, we should only consider the first min(k, n) rows or cols thus, the time complexity is... let m = min(k, n), O(m * klgm), a whole lot better than polynomial complexity! note: there is also a way to solve via binary search which seems to have even faster complexity
347. Top K Frequent Elements Given a non-empty array of integers, return the k most frequent elements. Note: You may assume k is always valid, 1 ≤ k ≤ number of unique elements. Your algorithm's time complexity must be better than O(n log n), where n is the array's size. It's guaranteed that the answer is unique, in other words the set of the top k frequent elements is unique. You can return the answer in any order.
Insight/Solution: Heap We can use a hash table to keep count of each number, sort the numbers by count, then take the top k elements we could just sort the counts and take the top k elements, but the sorting would take O(nlgn), which is actually not the optimal solution we could optimize our algorithm to O(nlgk) using a heap, counting the numbers takes O(n), building a heap of just k elements takes O(klgk) (but big theta of k), then adding/removing the rest of the elements which takes O((n - k)lgk) because each operation is O(lgk) and we do it n - k times, we sum up the complexities and end up with O(nlgk)
103. Binary Tree Zigzag Level Order Traversal Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between).
Insight/Solution: BFS + Deque, for each level, utilize a different order property (FIFO, then LIFO)
31. Next Permutation Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. If such an arrangement is not possible, it must rearrange it as the lowest possible order (i.e., sorted in ascending order). The replacement must be in place and use only constant extra memory. Constraints: 1 <= nums.length <= 100 0 <= nums[i] <= 100
Insight/Solution: Array
453. Minimum Moves to Equal Array Elements Given a non-empty integer array of size n, find the minimum number of moves required to make all array elements equal, where a move is incrementing n - 1 elements by 1.
Insight/Solution: Array
4. Median of Two Sorted Arrays Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays. Follow up: The overall run time complexity should be O(log (m+n)). Constraints: nums1.length == m nums2.length == n 0 <= m <= 1000 0 <= n <= 1000 1 <= m + n <= 2000 -106 <= nums1[i], nums2[i] <= 106
Insight/Solution:
73. Set Matrix Zeroes Given an m x n matrix. If an element is 0, set its entire row and column to 0. Do it in-place. Follow up: A straight forward solution using O(mn) space is probably a bad idea. A simple improvement uses O(m + n) space, but still not the best solution. Could you devise a constant space solution? Constraints: m == matrix.length n == matrix[0].length 1 <= m, n <= 200 -231 <= matrix[i][j] <= 231 - 1
Insight/Solution: Array + index marking, this problem sucks (it's a good problem, just super tricky to deal with) you can use a set of rows and cols to handle which rows or columns need to be set to 0, but that will take O(m + n) space a constant space solution requires index marking, so here is the expression: if cell[i][j] == 0 { cell[i][0] = 0 cell[0][j] = 0 note: make sure to not zeroify matrix[0][0], since that is a special edge case that has to zeroify a row and a column, but there are cases when matrix[0][0] becomes 0 but row 0 does not get completely zeroified since any integer is fair game, we can only use 0 as a marker, but that presents plenty of problems if we stumble on a 0 that occurred when it shouldn't have, but notice that the 0s that we have to consider for when we decide to zeroify is all on the left and top border, so we can iterate inside that border first to zeroify now, check the borders, we know that the top border must be zeroified if matrix[0][0] == 0, and keep track of a boolean variable that lets us know if matrix[i][0] == 0, which would mean we have to zeroify the left border as well this problem is still pretty confusing, so review it again
54. Spiral Matrix Given an m x n matrix, return all elements of the matrix in spiral order. Constraints: m == matrix.length n == matrix[i].length 1 <= m, n <= 10 -100 <= matrix[i][j] <= 100
Insight/Solution: Arrays + Simulation you pretty much do what it literally asks, traverse in a spiral manner, which will require 4 pointers: top, left, right, and bottom the tricky part about this problem is the indexing and how to go about the simulation, but it can be easily thought when you have this mindset: after traversing some direction, we no longer can go that path, this may sound confusing but will make sense with an example at the start, we go top left to top right, so after traversing the top, we no longer need to access that top path, so we can increment our top pointer by 1, that is the idea we need to safely solve this problem
1197. Minimum Knight Moves In an infinite chess board with coordinates from -infinity to +infinity, you have a knight at square [0, 0]. A knight has 8 possible moves it can make, as illustrated below. Each move is two squares in a cardinal direction, then one square in an orthogonal direction. Return the minimum number of steps needed to move the knight to the square [x, y]. It is guaranteed the answer exists. Constraints: |x| + |y| <= 300
Insight/Solution: BFS the problem screams BFS lol but there is an optimization we can do, we observe that our space is way too large, in other words, there is no reason to explore other areas if we can only go in a general one or two directional way to our solution we shrink the searching space by also recognizing that the movements we make are all symmetrical, so we could only observe one quadrant instead of four (positive and negative numbers do not matter) for simplicity, we can choose the first quadrant (+, +) as our solution space by taking the absolute values of x and y, there is a catch though, there are spots where it would take more spots than the minimum if we solely restrict our space to the first quadrant (i.e. (1, 1)), for these special spots, we actually have to step out of the first quadrant ONLY ONCE, then move back to the right spot (in this case, took 2 moves), so we could instead expand our solution space by a maximum of the first quadrant and 2 more spaces in the negative directions time complexity is tricky to figure out, but it is O(|xy|)
662. Maximum Width of Binary Tree Given a binary tree, write a function to get the maximum width of the given tree. The maximum width of a tree is the maximum width among all levels. The width of one level is defined as the length between the end-nodes (the leftmost and right most non-null nodes in the level, where the null nodes between the end-nodes are also counted into the length calculation. It is guaranteed that the answer will in the range of 32-bit signed integer. Constraints: The given binary tree will have between 1 and 3000 nodes.
Insight/Solution: BFS + Tree indexing, it is clear that we can use BFS to solve the problem, but how does tree indexing come into play? Consider a heap, it can be built with only an array and some clever indexing, the same idea applies with this problem in terms of indexing parent = i left = 2 * i right = 2 * i + 1 the left most node's index will be left and the right most node's index will be right, the width = right - left + 1 so at each level, update the width to take the maximum width, this can be done via BFS
116. Populating Next Right Pointers in Each Node You are given a perfect binary tree where all leaves are on the same level, and every parent has two children. The binary tree has the following definition: struct Node { int val; Node *left; Node *right; Node *next; } Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL. Initially, all next pointers are set to NULL. Follow up: You may only use constant extra space. Recursive approach is fine, you may assume implicit stack space does not count as extra space for this problem. Constraints: The number of nodes in the given tree is less than 4096. -1000 <= node.val <= 1000
Insight/Solution: BFS + concept of Linked List standard BFS algorithm, but use properties of a double ended queue to complete the algorithm Follow-up: this extra constraint eliminates our use of a data structure, so we have to use pointer references instead, the thing about our problem is that we can simulate level order traversal with some clever thinking, and the key lies in the next pointer consider a binary tree that is formed as follows: [1,2,3,4,5,6,7] (use heap's indexing), then we notice that to make the connection from 2 to 3, we simply can do so because both 2 and 3 share the same parent (same can be applied to 4 and 5, 6 and 7) but what about the connection from 5 to 6? they don't share a common parent, but the parent of 5 is 2, which has a next pointer to the parent of 6, which is 3! what makes this problem easier is that know for a fact that the only way we have this case is for a when a parent's right child has to point to another parent's left child, this is assumed true because we are working with a perfect binary tree which means we have two cases that can be solved these ways: 1. the node's left child points to the node's right child 2. let there be parent A and parent B, then parent A's right child points to parent B's left child, only if parent A points to parent B to handle each level, we have to consider two levels at once and this is also possible thanks to the root's next always being null, to make our next connections, we need to iterate through the parents as well, which we simply do by setting parent = parent.next, in essence, a linked list traversal
117. Populating Next Right Pointers in Each Node II Given a binary tree struct Node { int val; Node *left; Node *right; Node *next; } Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL. Initially, all next pointers are set to NULL. Follow up: You may only use constant extra space. Recursive approach is fine, you may assume implicit stack space does not count as extra space for this problem. Constraints: The number of nodes in the given tree is less than 6000. -100 <= node.val <= 100
Insight/Solution: BFS + previous pointers for next similar problem to 116 except it now uses a binary tree, so there is more room for interesting cases but the idea is still straightforward, if you just do a level order traversal, you will get the answer, so simply conduct a BFS + queue and you'll have a solution Follow-up: this problem is pretty tricky to handle, but nevertheless still straightforward, we need to keep track of each level's current node, the head of the next level, and the leading node of the next level, since again, we are going to look through the tree in a way where connect the nodes when the nodes are children of curr if curr has a left child, we need to see if prev is not null, then we know there is a previously leading node so it's next will point to curr.left, but if it is null, we know that this node is the first node of the level, so we set head = curr.left, afterwards, we want to move to the next node in the level, so prev now equals curr.left, indicating that it was the previously leading node of the level now check if curr has a right child, and do the exact same thing except with curr.right now we move curr until all the nodes in that level has been visited after that, head will be the first node of the next level, so set curr = head, and reset prev and head to null so we can handle the next level
107. Binary Tree Level Order Traversal II Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from left to right, level by level from leaf to root).
Insight/Solution: BFS then reverse the order
102. Binary Tree Level Order Traversal Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level).
Insight/Solution: BFS, the most standard BFS algorithm on the tree
698. Partition to K Equal Sum Subsets Given an array of integers nums and a positive integer k, find whether it's possible to divide this array into k non-empty subsets whose sums are all equal. Note: 1 <= k <= len(nums) <= 16. 0 < nums[i] < 10000.
Insight/Solution: Backtracking although the problem is somewhat related to 416, we can solve this problem using backtracking, and I think it is much easier to consider this problem using backtracking instead of dynamic programming recognize that the subset_sum must be the total_sum // k, if this cannot happen, we must return False they key to the algorithm is to use a nested recursion, our outer recursion would be when k == 0, return True, otherwise if the subset_sum and the path_sum are the same, call dfs again but decrementing k, otherwise, run a for loop to check each number in nums whether the number has not been used, which then we would recursively call dfs again to look towards another number
526. Beautiful Arrangement Suppose you have n integers labeled 1 through n. A permutation of those n integers perm (1-indexed) is considered a beautiful arrangement if for every i (1 <= i <= n), either of the following is true: perm[i] is divisible by i. i is divisible by perm[i]. Given an integer n, return the number of the beautiful arrangements that you can construct. Constraints: 1 <= n <= 15
Insight/Solution: Backtracking the problem is literally finding permutations except we throw in an extra condition: in addition to checking if it was in the seen set, we also determine if the position we are on % i or i % the position are 0, if either is, we call backtrack, if not, move on to the next i time complexity should still be O(n!)
47. Permutations II Given a collection of numbers, nums, that might contain duplicates, return all possible unique permutations in any order. Constraints: 1 <= nums.length <= 8 -10 <= nums[i] <= 10
Insight/Solution: Backtracking + Counter The idea is to recognize that backtracking is about choices, and the choices we should make for this problem are unique numbers, not each individual number we can simply use a hash table to keep track of each count of unique numbers, and iterate accordingly and place the number in a guaranteed path this is guaranteed because we ensure that by iterating only the unique numbers, we do not run into a duplicate and have to handle cases using sort + [i] == [i - 1] + another condition, for a visual, draw a recursive tree for [1,1,2] where we iterate only the true candidates (unique numbers), that means each tree should branch twice, since we have two true candidates to choose from
40. Combination Sum II Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sum to target. Each number in candidates may only be used once in the combination. Note: The solution set must not contain duplicate combinations. Constraints: 1 <= candidates.length <= 100 1 <= candidates[i] <= 50 1 <= target <= 30
Insight/Solution: Backtracking + Sort the problem builds on 39. Combination Sum, where now we have the condition to only use one candidate, this also means we have to handle duplicates we can handle duplicates by sorting and checking for adjacent duplicates, which we would just skip if there are any, to handle using only one candidate, we keep track of a pointer and always advance our pointer after a backtrack call
90. Subsets II Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set). Note: The solution set must not contain duplicate subsets.
Insight/Solution: Backtracking + Sort, the problem is similar to 78. Subsets the key difference is that we cannot have duplicates, and the way to handle this case is to sort the numbers first, that way, when we have an order like [2, 1, 2], we can sort it to [1,2,2] and check if the adjacent element was a duplicate or not there is a catch though, there will be a case where we stumble upon [1,2] and [1,2], where each 2 is from the 1st and 2nd index of the array [1,2,2], also, [2,2] is a valid subset, we can handle this case by only skipping when we have reached this condition: if i > index and nums[i] == nums[i - 1] i can only be >= index to begin with, so why not i == index? we miss out on a subsets that have duplicates in the subset but isn't an actual duplicate subset, since we work in the manner of a top-down approach, so when we have a path of [1,2], then the remaining path we have left is [2], which would not ever be checked had i == index been part of the condition
162. Find Peak Element A peak element is an element that is strictly greater than its neighbors. Given an integer array nums, find a peak element, and return its index. If the array contains multiple peaks, return the index to any of the peaks. You may imagine that nums[-1] = nums[n] = -∞. Constraints: 1 <= nums.length <= 1000 -231 <= nums[i] <= 231 - 1 nums[i] != nums[i + 1] for all valid i. Follow up: Could you implement a solution with logarithmic complexity?
Insight/Solution: Binary Search you can linearly scan, and whenever nums[i] > nums[i + 1], i is a peak, do this until nums.length - 1, and return nums.length if you couldn't find a peak via the first condition Follow-up: use binary search, consider the following: if nums[n // 2] < nums[n // 2 - 1], then we are coming from a peak, so search the left side elif nums[n // 2] < nums[n // 2 + 1], then we are coming to a peak, so search the right side otherwise, n // 2 is the peak location for situations where we reach an edge, the answer will always be at the left pointer, since we narrow down to a size of 1 if we have a peak at the edge
240. Search a 2D Matrix II Write an efficient algorithm that searches for a target value in an m x n integer matrix. The matrix has the following properties: Integers in each row are sorted in ascending from left to right. Integers in each column are sorted in ascending from top to bottom. Constraints: m == matrix.length n == matrix[i].length 1 <= n, m <= 300 -109 <= matix[i][j] <= 109 All the integers in each row are sorted in ascending order. All the integers in each column are sorted in ascending order. -109 <= target <= 109
Insight/Solution: Binary Search, Adaptive Searching For binary search, we simply check for each row to see if the row could be a candidate, then perform binary search if so, the complexity would be O(mlgn) however, we can reduce the complexity to O(m + n) by consider the matrix almost like a tree, if we start at a root value at the bottom left corner, we could effectively make decisions whether to move up or to the right, we essentially take advantage of the fact that both rows and columns are sorted if matrix[row][col] > target, we decrement our row pointer else if matrix[row][col] < target, we increment our col pointer else we have a match! by placing our root at the bottom left corner, we can assume that the row is at the highest capacity it can be to begin with, so whenever the matrix value is still less than the target, we know to move the column pointer, the same goes for the row pointer
437. Path Sum III You are given a binary tree in which each node contains an integer value. Find the number of paths that sum to a given value. The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes). The tree has no more than 1,000 nodes and the values are in the range -1,000,000 to 1,000,000.
Insight/Solution: DFS + Prefix sum, literally 560. Subarray Sum Equals K but as a tree, the only special condition is that we have to consider both the left side and the right side, so after DFSing on both sides, subtract the hash table value of curr_sum by 1 (almost could consider it backtracking)
113. Path Sum II Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum. Note: A leaf is a node with no children.
Insight/Solution: DFS, a continuation of 112. Path Sum the additional part of code we have to add is simply a way to store all the possible paths, so use the backtracking framework on the idea of 112. Path Sum except there is no backtracking
91. Decode Ways A message containing letters from A-Z can be encoded into numbers using the following mapping: 'A' -> "1" 'B' -> "2" ... 'Z' -> "26" To decode an encoded message, all the digits must be mapped back into letters using the reverse of the mapping above (there may be multiple ways). For example, "111" can have each of its "1"s be mapped into 'A's to make "AAA", or it could be mapped to "11" and "1" ('K' and 'A' respectively) to make "KA". Note that "06" cannot be mapped into 'F' since "6" is different from "06". Given a non-empty string num containing only digits, return the number of ways to decode it. The answer is guaranteed to fit in a 32-bit integer. Constraints: 1 <= s.length <= 100 s contains only digits and may contain leading zero(s).
Insight/Solution: Dynamic Programming idea is to really use conditional statements to prevent any errors for instance, if there is a leading zero, it is instantly impossible to decode 0 cannot be a decoded letter in its own, and only numbers 10 and 20 are numbers that can be decoded and also have 0 as a digit the recursive expression has to do with: number of ways = { 0, if leading zero 1, if string is empty dp[1:], if string.length > 0 dp[1:] + dp[2:], if string.length >= 2 and dp[0:2] is a number between 10 and 26
338. Counting Bits Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1's in their binary representation and return them as an array. Follow up: It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass? Space complexity should be O(n). Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.
Insight/Solution: Dynamic Programming, Bit Manipulation for each number, count the number of 1 bits and append to a result list Follow-up: the best way to see this is to get all the bit strings for the range 0 to 8, and when you take a look at numbers such as 3 and 6, their bit strings are 011 and 110, respectively these bit strings are shifted by 1! they have the same number of 1s, just shifted, which means we know that for even numbers, we can simply find the number of 1 bits by dividing the number by 2 and find the number of 1 bits for that number this isn't the case for odd numbers though, let's take a look at 2 and 3 which are 10 and 11, well... we just have an extra 1 bit for 3, and this makes sense since 3 = 2 + 1, so naturally we just add 1 to the number of bits for 2, and this works for all even numbers so we end up with this relationship dp[i] = { dp[i >> 1] or dp[i // 2], if i is even dp[i - 1] + 1, if i is odd note: i >> 1 is the same as i // 2, conversely, i << 1 is the same as i * 2
55. Jump Game Given an array of non-negative integers nums, you are initially positioned at the first index of the array. Each element in the array represents your maximum jump length at that position. Determine if you are able to reach the last index. Constraints: 1 <= nums.length <= 3 * 104 0 <= nums[i] <= 105
Insight/Solution: Dynamic Programming, Greedy this problem is a good example of a problem that can be solved using either DP or greedy but the greedy is the optimal solution (usually DP is optimal and greedy is not) Using DP, we recognize that we just need to find all the combinations, so we can use backtracking + memoization, the time complexity would be O(n^2) Using greedy, recognize that we don't necessarily have to determine whether the non-maximum jumps yield to the last position or not, instead, we recognize that if we our current position + the maximum jump is greater than the last position, we know that the last position is possible to reach It would be wise to start from the end to the start as well, since that way, we can solve for the condition more efficiently so to set up the greedy algorithm: good_spot = nums.length - 1 then while iterating in reverse: if i + nums[i] >= good_spot, set good_spot = i if i == 0 at the end, we know that we can jump from the start to the end
416. Partition Equal Subset Sum Given a non-empty array nums containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal. Constraints: 1 <= nums.length <= 200 1 <= nums[i] <= 100
Insight/Solution: Dynamic Programming, in particular the 0/1 Knapsack Problem what we come to realize is we can decide whether or not to include an item in our knapsack, in this case, whether we pick a number to include in our subset But how do we what our weight limit should be for the subset? recognize that if we have 2 subsets of the same sum, then the total sum of the subset must be 2 * subset_sum, so we can easily calculate subset_sum by computing total_sum // 2, since we can only work with positive integers, we cannot find such a partition for two subsets if the total_sum is odd so the problem deduces down to finding a subset that adds up to half of the total_sum, and we have the power to decide whether we include an item in the subset or not our recursive expression then becomes: dp[0][0] = True, base case dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]], if j >= nums[i] where i represents the subset_sum and j represents the index of an item to pick for our subset
767. Reorganize String Given a string S, check if the letters can be rearranged so that two characters that are adjacent to each other are not the same. If possible, output any possible result. If not possible, return the empty string. Note: S will consist of lowercase letters and have length in range [1, 500].
Insight/Solution: Greedy + Heap the idea is to fill up our characters with the highest frequency, but not consecutively (instead, in the manner of every other character, when possible) we can use a max-heap (compared via frequencies) to accomplish this problem, we can also use prev_count and prev_char to store counts and chars and use it as a policing system to not have the same adjacent letters, there are many other ways to solve the problem using a max-heap, but this is one of them so pop the highest counted character (let's call them curr_count and curr_char), append that character into a string builder, then push the PREVIOUS count and character into the heap, since curr_char was just added to the string builder, increment curr_count, then set prev_count and prev_char with curr_count, curr_char we can only push the PREVIOUS count and character into the heap if prev_count has not reached 0, the greedy choice we make is to fill up the string with the one with highest frequency first return the string builder as a string afterwards note: make sure to handle cases where it is impossible to avoid adjacent, similar chars, we check if the most frequency count is greater than half the length of a string
435. Non-overlapping Intervals Given a collection of intervals, find the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping. Note: You may assume the interval's end point is always bigger than its start point. Intervals like [1,2] and [2,3] have borders "touching" but they don't overlap each other.
Insight/Solution: Greedy + Intervals the problem is really similar to the activity selection problem, except whenever we do have an overlapping interval, we increment a count return the count afterwards
621. Task Scheduler Given a characters array tasks, representing the tasks a CPU needs to do, where each letter represents a different task. Tasks could be done in any order. Each task is done in one unit of time. For each unit of time, the CPU could complete either one task or just be idle. However, there is a non-negative integer n that represents the cooldown period between two same tasks (the same letter in the array), that is that there must be at least n units of time between any two same tasks. Return the least number of units of times that the CPU will take to finish all the given tasks. Constraints: 1 <= task.length <= 104 tasks[i] is upper-case English letter. The integer n is in the range [0, 100].
Insight/Solution: Greedy, Math you can use math, but it is pretty similar to the greedy approach, I am going to go over the greedy approach since I found it make more sense to me the idea is that we tackle the character with the most frequency first, that will minimize the number of idle time we need because we can use the other tasks to fill in what an idle could potentially be placed the best way to examine this algorithm is to use an example: [A,A,A,B,B,B,C,D], n = 3 1. A ? ? ? A ? ? ? A 2. A B ? ? A B ? ? A B 3. A B C ? A B ? ? A B 4. A B C D AB ? ? A B 5. A B C D A B idle idle A B i.e. of not optimal sequence: D C B A idle idle B A idle idle B A as you can see, if we greedily solve for the most frequent tasks, we can minimize the number of idles (we will have less idles to fill in between restricted time intervals) notice that at 1). we see that there are 6 empty spots to fill, our job is to fill whatever tasks we can into those spots and count up whatever left is available as our idle times, so here is what we want to do: - find the remaining spots available after filling in our most frequently used task - fill the spots with our next most frequently used task here is the catch, what if we have a different task with the same frequency as the max? not only does that task fill 1 spot for each ? part, it also creates a new spot (see the B after the A at the end?), it does not take up 2 spots in a ? part, so we still subtract only up to f_max - 1 spots so our full algorithm is as follows: - count frequencies and sort them in ascending order - initialize f_max with the max frequency and idle_times with (f_max - 1) * n (this is ? count) - iterate while frequencies is not empty and idle_times > 0, subtract idle_times by min(f_max - 1, freq.pop()), this min function handles the duplicate edge case - idle_times could go negative, but that doesn't make sense, so we have a max function for max(0, idle_times) - add length of tasks and idle_times for your answer
692. Top K Frequent Words Given a non-empty list of words, return the k most frequent elements. Your answer should be sorted by frequency from highest to lowest. If two words have the same frequency, then the word with the lower alphabetical order comes first. Note: You may assume k is always valid, 1 ≤ k ≤ number of unique elements. Input words contain only lowercase letters. Follow up: Try to solve it in O(n log k) time and O(n) extra space.
Insight/Solution: Hash Table + Heap + Custom Comparator we can use a max heap to solve this problem, we count up the frequencies for each word and store them with a hash table we then build an array for frequencies then words, max-heapify it, then pop the top k words into an array, the total time complexity is dominated by O(nlgn) worst case Follow-up: We want O(nlgk) which means the heap size should never exceed k, and we can do this by using a min-heap instead, here is the catch: what happens when two words have the same frequency? we must use a custom comparator to handle this edge case the custom comparator should suggest to give a word a higher value if its word starts before the other word, and by using a comparator, we can implement the algorithm just like if we were solving the problem for only its frequency, all while using a min-heap to maintain the size of k afterwards, we pop one by one k times into a list, and return that list in reverse
560. Subarray Sum Equals K Given an array of integers nums and an integer k, return the total number of continuous subarrays whose sum equals to k. Constraints: 1 <= nums.length <= 2 * 104 -1000 <= nums[i] <= 1000 -107 <= k <= 107
Insight/Solution: Hash Table + prefix sum right off the bat, you might imagine sliding window, but we cannot use it because there are negative inputs, which means we could have a sum outside of the window that satisfies our condition (due to the negative number) instead, we have to use something called a prefix sum, which is something like this y0 = x0 y1 = x0 + x1 y2 = x0 + x1 + x2 ... we could have a curr_sum and a prefix_sum, both of which are storing variable and hash table, respectively suppose k = x1 + x2, then we know that when y2 = x0 + x1 + x2 and y0 = x0, then y2 - y0 = x0 + x1 + x2 - x0 = x1 + x2 = k now lets generalize them into different variables: a - b = k, where a is a longer prefix sum than b, then a - k = b, and since b is smaller than a, we know that b was seen before a, we can use a hash table to keep track of which prefix sums we have already seen, so we can simply check if b is in the hash table and decide whether we increment our count or not
973. K Closest Points to Origin We have a list of points on the plane. Find the K closest points to the origin (0, 0). (Here, the distance between two points on a plane is the Euclidean distance.) You may return the answer in any order. The answer is guaranteed to be unique (except for the order that it is in.) Note: 1 <= K <= points.length <= 10000 -10000 < points[i][0] < 10000 -10000 < points[i][1] < 10000
Insight/Solution: Heap, Divide and Conquer (Quickselect) First, know that the distance formula for a point to the origin is sqrt(x ^ 2 + y ^ 2), so we need to find the points with the smallest distances from the origin to the points, return the K smallest of them this should scream out a heap, and we can do so by constructing a max heap on the fly, and whenever the size of our heap is over K, we pop the head of the heap (which would be the max), then we will eventually be left with the K smallest elements note that sqrt(x ^ 2 + y ^ 2) is correlated with x ^ 2 + y ^ 2, so no need to sqrt since we maintain a size of K for our heap, each push and pop operation costs lgK, and we do it N times, so our complexity would be O(NlgK) we can actually do even better with quickselect, essentially just quick sort the points by distance and return the first K coordinates in points
57. Insert Interval Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary). You may assume that the intervals were initially sorted according to their start times. Constraints: 0 <= intervals.length <= 104 intervals[i].length == 2 0 <= intervals[i][0] <= intervals[i][1] <= 105 intervals is sorted by intervals[i][0] in ascending order. newInterval.length == 2 0 <= newInterval[0] <= newInterval[1] <= 105
Insight/Solution: Interval you could solve this problem by appending the newInterval to intervals, then treating the problem like 56. Merge Intervals, but we can take advantage of the intervals already being sorted, that way, we maintain O(n) and downgrade to O(nlgn) simply just iterate through each interval until you find a suitable spot to place the newInterval, then keep merging for the rest of the intervals
56. Merge Intervals Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input. Constraints: 1 <= intervals.length <= 104 intervals[i].length == 2 0 <= starti <= endi <= 104
Insight/Solution: Interval + Sorting there are two kinds of interval merges: - [a,b], [c,d] and b >= c and d > b -> [a,d] - [a,b], [c,d] and b > c and b >= d -> [a,b] in other words, an interval either merges or the dominant one completely absorbs the other interval we can handle the intervals by first sorting them by start time, then merging accordingly by the start and end times of the intervals in question Note: to avoid having to pop or have indexing errors, use another list to hold the newly merged intervals
252. Meeting Rooms Given an array of meeting time intervals where intervals[i] = [starti, endi], determine if a person could attend all meetings. Constraints: 0 <= intervals.length <= 104 intervals[i].length == 2 0 <= starti < endi <= 106
Insight/Solution: Intervals + Sort the problem is really straightforward, and the idea can be similar to the activity selection problem, if we ever have an overlapping interval, we cannot attend all meetings now, the problem is that the intervals are out of order, so the best way to determine the eligibility is by sort, and in this case, we use a special sort in which we sort by the start time here is the expression: intervals[i][1] > intervals[i + 1][0], return False return True if all intervals do not overlap it is possible to do it by end time, but you would have to modify the expression
83. Remove Duplicates from Sorted List Given the head of a sorted linked list, delete all duplicates such that each element appears only once. Return the linked list sorted as well. Constraints: The number of nodes in the list is in the range [0, 300]. -100 <= Node.val <= 100 The list is guaranteed to be sorted in ascending order.
Insight/Solution: Linked List simply a manipulation application of pointers, set up a traveling pointer to do two things: - if the traveler's value == traveler's next's value, we have a duplicate and so set traveler's next now to traveler's next's next - otherwise, we do not have a duplicate, so we check the next number for duplicates, so move traveler to traveler's next the correctness for this algorithm can be proven using a loop invariant: - initially we have a pointer set to the head, and before it there are no duplicates - maintain this property with our two conditions we elaborated above - terminate once traveler's next is null (in essence, reach the end of the linked list)
1290. Convert Binary Number in a Linked List to Integer Given head which is a reference node to a singly-linked list. The value of each node in the linked list is either 0 or 1. The linked list holds the binary representation of a number. Return the decimal value of the number in the linked list. Constraints: The Linked List is not empty. Number of nodes will not exceed 30. Each node's value is either 0 or 1.
Insight/Solution: Linked List + Bit Manipulation there are two subproblems to this problem: traversing the linked list and converting the binary number to decimal... on the fly brute force solution would be storing the bits into a list, reversing it, then converting it that way however, we can do this in O(1) space and on the fly, thanks to bit manipulation (and without reversing) we set a value as 0, then what we notice is that we could shift the bit as we move along, so i.e., we have a linked list 1->0->1, value goes from 0 to 1 after visiting the head, and when we visit the next node, we shift our value to the left which becomes 10 in binary, then we simply add the head's value which we can do with | operator so the while loop would look like this: while head is not null - value <<= 1 - value |= head.val - move head to the next node
92. Reverse Linked List II Reverse a linked list from position m to n. Do it in one-pass. Note: 1 ≤ m ≤ n ≤ length of list.
Insight/Solution: Linked List + Dummy node this problem elevates reverse a linked list further, now our new constraints include only reversing the linked list from m to n we can follow the same logic for reversing the linked list entirely, except now we can use a dummy node since it will be very helpful in keeping track of the head of the linked list so first make a dummy node and set its next pointer to head, then have prev = dummy iterate prev = prev.next by m - 1 times, we do m - 1 times because prev will be the node before m set curr = prev.next, then reverse the linked list accordingly (DO NOT USE prev, use reverse = null in place for prev) prev plays a big role in connect the rest of the linked list, if you draw out the diagram, it'll make sense, connect prev.next.next with curr and prev.next with reverse then you can return dummy.next, also check the case for reversing the linked list as a whole too, the thought process is extra tricky but there is no need to make any modifications, you'll see the true role of prev when you draw the diagram (also remember that pointers are modified by reference!)
328. Odd Even Linked List Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes. You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity. Constraints: The relative order inside both the even and odd groups should remain as it was in the input. The first node is considered odd, the second node even and so on ... The length of the linked list is between [0, 10^4].
Insight/Solution: Linked List + Fast and Slow Pointers have three pointers, an odd pointer pointing to head, even pointer pointing to head.next, and even_head pointing to even we essentially have both odd and even pointers point to their next.next, so we iterate this until even or even.next is null once we are there, odd pointer will have reached the end of its odd portion, so it can directly point back to even_head, which is the start of even return head after
86. Partition List Given the head of a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x. You should preserve the original relative order of the nodes in each of the two partitions. Constraints: The number of nodes in the tree is in the range [0, 200]. -100 <= Node.val <= 100 -200 <= x <= 200
Insight/Solution: Linked List + Two Pointers first realize that the problem does not ask for a sorted linked list if we simply have to partition the linked list such that the left side is less than x and the right side greater than or equal to x, we can build two new linked lists and combine them together at the end, we don't care about the sorted order, but we do care about their relative orders which this algorithm will satisfy have two dummy nodes for before_x and after_x, then build them up, connect them together, and you have the linked list
24. Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. Constraints: The number of nodes in the list is in the range [0, 100]. 0 <= Node.val <= 100 Follow up: Can you solve the problem without modifying the values in the list's nodes? (i.e., Only nodes themselves may be changed.)
Insight/Solution: Linked List + in-place reversal + dummy node idea is to just iterate through each pair and swap the values Follow-up: welp, just do it with pointers now! make sure to use a dummy node since that will help out a lot in the long run, it allows us to make the third swap we need third swap?? yes! just take 1->2->3->4->null for example, and swap 3->4 instead of 1->2 to realize the issue, you'll realize that 2 will continue pointing to 3, but it should point to 4, that is the third swap to make
203. Remove Linked List Elements Remove all elements from a linked list of integers that have value val.
Insight/Solution: Linked List + sentinel node similar problem to 83. Remove Duplicates from Sorted List except now we have to eliminate specific nodes, handle this just like 83 except a few extra conditions, we could have the head completely removed, so using a dummy node will be beneficial in handling this edge case, if we move the end node, we set our traveling pointer to null so we have to also terminate when our traveling pointer is at null if this explanation sucks, it is because I'm too lazy to type out the same exact solution we had in 83, but I solved it without any hints so I'm sure you can solve it again
442. Find All Duplicates in an Array Given an array of integers, 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once. Find all the elements that appear twice in this array. Could you do it without extra space and in O(n) runtime?
Insight/Solution: Marking indexes Sure, there's the brute force, sort, and set methods, but if we can modify the given array, then we can mark them by multiplying by -1 It can be a little tricky, but essentially we do the following: - iterate through num in nums - if nums[abs(num) - 1] < 0, we know it is a duplicate - set nums[abs(num) - 1] to negative essentially, we mark if we have already seen the element by multiplying it by -1 this problem uses the same idea from 448. Find All Numbers Disappeared in an Array
239. Sliding Window Maximum You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window.
Insight/Solution: Sliding Window + deque you might consider using a max-heap, but that will not improve the complexity compared to the brute force solution, instead we use the deque to act like a max-heap while maintaining properties useful for sliding windows initially, loop from i = 0 to k and set up the deque, the properties we need our deque to have is as follows: - deq stores only indexes - deq[0] stores the index of the maximum value - deq's elements are indexes ordered in decreasing nums[i] order we also have to make sure we maintain the window size, so we have to dequeue the deque when an element in the deque falls out of the window (check this first because we have to make sure we do not consider the element if it does fall out of the window) now that the first window is completed, we can proceed to finish the rest of the windows and return the maximum values of each window
253. Meeting Rooms II Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required. Constraints: 1 <= intervals.length <= 104 0 <= starti < endi <= 106
Insight/Solution: Sort + Heap + Intervals very similar to 252 but with a twist, we have to keep track of all the rooms required the idea with sort and interval from 252 will continue to apply for 253, the new additional problem is we have to keep track of the number of rooms and how recognize that if we simply come up with a room that doesn't overlap an interval, we know that it can replace the room that just finished, this room will also have to be the room with the fastest (smallest?? idk the word to use here) end time, and we can easily keep track via a min heap, and as a result, the min heap acts as a room counter in itself once we go through all intervals, we can simply return the length of our min heap which will represent the number of rooms
15. 3Sum Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero. Notice that the solution set must not contain duplicate triplets. Constraints: 0 <= nums.length <= 3000 -105 <= nums[i] <= 105
Insight/Solution: Sort + Two Pointers Notice that a + b = -c, so we could downgrade this problem into two sum, however, since we do have duplicates to consider, we can easily handle duplicates by sorting and skipping adjacent duplicates so actually, the problem downgrades to Two Sum sorted here's the catch, when we do find a triplet of numbers, we have to ensure we don't append the same triplet, so make sure you handle duplicates right after you append a triplet as well
16. 3Sum Closest Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one solution.
Insight/Solution: Sort + Two pointers this problem is more straightforward than 15. 3Sum, no real tricky part about the problem where we have to handle duplicates instead, what we need to do is first still sort the array, then iterate from i = 0 to nums.length - 2 then have a left pointer and a right pointer, and a total variable which sums nums[i] + nums[l] + nums[r], the trick now is to find the smallest complement, so we can also use the absolute values property to just gauge the distance, not the actual values so if abs(target - total) < abs(diff), we know to update diff by target - total, then we decide whether to move the left pointer or the right pointer by comparing total with target note that we cannot absolute value the target - total value for diff because at the end, we will return target - diff = target - (target - total) = total if you absolute value the target - total, that could result in 2*target - total
75. Sort Colors Given an array nums with n objects colored red, white, or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue. We will use the integers 0, 1, and 2 to represent the color red, white, and blue, respectively. Follow up: Could you solve this problem without using the library's sort function? Could you come up with a one-pass algorithm using only O(1) constant space?
Insight/Solution: Sort, Two (Three) Pointers sort the array in place, lol Follow-up: this problem is known as the Dutch National Flag problem and the famous algorithm to solve it is created by Dijkstra suppose you have three pointers: p1 represents the rightmost side of all 0s, p2 represents the leftmost side of all 2s, and curr represents the current element in question the idea is to make swaps depending on what nums[curr] is, if it is 0, we swap nums[curr] with nums[p1] and increment both pointers, if it is 2, we swap nums[curr] with nums[p2] and decrement p2, if it is 1, move curr pointer the proof of this algorithm is simply a loop invariant for both cases, to summarize, we maintain only 0s on the left side and 2s on the right side initially, p1 and curr are at 0, p2 is at the end of the list we terminate once curr crosses over p2, which means all of the 0s and 1s are to the left of p2
739. Daily Temperatures Given a list of daily temperatures T, return a list such that, for each day in the input, tells you how many days you would have to wait until a warmer temperature. If there is no future day for which this is possible, put 0 instead. For example, given the list of temperatures T = [73, 74, 75, 71, 69, 72, 76, 73], your output should be [1, 1, 4, 2, 1, 1, 0, 0]. Note: The length of temperatures will be in the range [1, 30000]. Each temperature will be an integer in the range [30, 100].
Insight/Solution: Stack or Queue the idea is we can work in reverse so we keep track of future temperatures, so if the current temperature is warmer or equal to the stack's top value's temperature, we know that there cannot be any warmer temperature in the future, so we know we can set the count = 0 for that temperature but if that does not happen to be the case, we can continue to pop the stack until we either find such a temperature or the stack becomes empty if the stack becomes empty, we know the count = 0, but if there is a temperature, we know that the number of temperatures in between those temperatures is the count we need to set, this can work perfectly if we store the index in the stack, that way we can access both the temperature location and the location itself at once we can actually do better and not use any data structure, the idea is to take advantage of our invariant, if we keep track of just the highest temperature, we can solve this with O(1) space, here's how it works we check the next day's temperature if the current temperature is not higher than the maximum temperature, if temp[i] >= temp[i + days], we can simply add days by count[i + days], because this is the distance between temp[i + days] and whichever next warmer temperature, so we pretty much have a new invariant such that temp[i + days] is always warmer than temp[i], so our invariant falls correctly we are guaranteed to find such a temperature as well since had there not been a warmer temperature, then we would've handled that case beforehand by setting the new highest temperature as temp[i]
921. Minimum Add to Make Parentheses Valid Given a string S of '(' and ')' parentheses, we add the minimum number of parentheses ( '(' or ')', and in any positions ) so that the resulting parentheses string is valid. Formally, a parentheses string is valid if and only if: It is the empty string, or It can be written as AB (A concatenated with B), where A and B are valid strings, or It can be written as (A), where A is a valid string. Given a parentheses string, return the minimum number of parentheses we must add to make the resulting string valid. Note: S.length <= 1000 S only consists of '(' and ')' characters.
Insight/Solution: Stack, Greedy stack: use a stack to keep track of which pairs are valid, just your traditional stack problem greedy: keep track of left_balance and right_balance, left_balance is for '(' and right_balance is for ')', we have to keep track of invalid '(' and ')', and we do so by checking each parenthesis and following these conditions: - increment left_balance if the symbol is '(' else decrement it - if left_balance reaches -1, we know that there is an extra right parenthesis which can never form a valid pair, so we increment right_balance by 1 as well as left_balance, we reset left_balance back to 0 to start fresh for a new pair
844. Backspace String Compare Given two strings S and T, return if they are equal when both are typed into empty text editors. # means a backspace character. Note that after backspacing an empty text, the text will continue empty. Note: 1 <= S.length <= 200 1 <= T.length <= 200 S and T only contain lowercase letters and '#' characters. Follow up: Can you solve it in O(N) time and O(1) space?
Insight/Solution: Stack, Two Pointers we can use two stacks, one for S and one for T, pop whenever the stack is not empty and the current char is '#', otherwise if the current char is not '#', put the char into the stack, then compare the two stacks, if they are the same, then we return true, else false note: for two pointers, we iterate in reverse since we will be "going into the future, and going back to the past" the other approach optimizes our space, and we use two pointers (very helpful to use a helper function), the idea is to keep track of how many "skips" we have, and a skip means a backspace, so when we reach a char with '#', we increment skips, but if we don't and skips is greater than 0, we decrement skips and keep moving forward (this means delete the previously seen character, so we can just disregard the character we are on), otherwise, we return the index location, keep decrementing the index value as you move forward, return -1 if we call this helper function once for both S and T, then we have a while loop, we exit out of the string when our pointer to S or T reaches -1 or when S[i] != T[j] in the body of our loop, we keep calling the helper function after the while loop, we check if i == j == -1, if that is true, then we have an identical backspace expression
863. All Nodes Distance K in Binary Tree We are given a binary tree (with root node root), a target node, and an integer value K. Return a list of the values of all nodes that have a distance K from the target node. The answer can be returned in any order. Note: The given tree is non-empty. Each node in the tree has unique values 0 <= node.val <= 500. The target node is a node in the tree. 0 <= K <= 1000.
Insight/Solution: Tree + Graph + Queue + BFS + Hash Table, the problem can be solved with just BFS if we don't have to consider going up as well, the BFS algorithm uses a queue and a hash set to keep track of nodes that have already been seen the problem is elevated when we have to consider going up as well, to understand how to handle this case, look back at what a tree is, it is also a special kind of graph, namely, a directed, acyclic, and connected graph, but if we want to go back to our parent (so get back to the top), we have to use an undirected graph, we can use a hash table to store the parents of each node and treat the tree now as an undirected graph afterwards, simply conduct the BFS which also handles parent nodes, once the level of the BFS reaches K, the queue will contain all the nodes at the Kth level Note: you have to also continuously check if curr_node's left, right, and parent have already been seen or not as well
211. Design Add and Search Words Data Structure Design a data structure that supports adding new words and finding if a string matches any previously added string. Implement the WordDictionary class: WordDictionary() Initializes the object. void addWord(word) Adds word to the data structure, it can be matched later. bool search(word) Returns true if there is any string in the data structure that matches word or false otherwise. word may contain dots '.' where dots can be matched with any letter. Constraints: 1 <= word.length <= 500 word in addWord consists lower-case English letters. word in search consist of '.' or lower-case English letters. At most 50000 calls will be made to addWord and search.
Insight/Solution: Trie + DFS, essentially a direct application of a Trie data structure, except we tweak the search method to accommodate for the wildcard '.' use DFS to search, and to handle the wildcard, simply iterate through each letter in the links of the current trie node and perform DFS
11. Container With Most Water Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of the line i is at (i, ai) and (i, 0). Find two lines, which, together with the x-axis forms a container, such that the container contains the most water. Notice that you may not slant the container. Constraints: n == height.length 2 <= n <= 3 * 104 0 <= height[i] <= 3 * 104
Insight/Solution: Two Pointers area = w * h w = r - l h = min(height[l], height[r]) area = max(area, w * h) then increment l if height[l] < height[r], else decrement r the idea is to recognize that although the width is varying, we know that the heights skew significantly, and we can only cap the area of the rectangle by the minimum height, there is a chance we could encounter a really large height after the smaller height, which is why we maintain the max height instead, its like investing into the future
977. Squares of a Sorted Array Given an integer array nums sorted in non-decreasing order, return an array of the squares of each number sorted in non-decreasing order. Constraints: 1 <= nums.length <= 104 -104 <= nums[i] <= 104 nums is sorted in non-decreasing order. Follow up: Squaring each element and sorting the new array is very trivial, could you find an O(n) solution using a different approach?
Insight/Solution: Two Pointers start with pointers l and r at 0 and at the end of the list, you can use a deque and appendleft whichever element has a greater absolute value, this solution works because the negative values are sorted in reverse order of what the end order will be we can also use a simple list with array indexing instead of a deque, since we build off the list from greatest to least, we can use the index [r-l] to place the next greatest value, it is effectively the same as appendleft
42. Trapping Rain Water Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining. Constraints: n == height.length 0 <= n <= 3 * 104 0 <= height[i] <= 105
Insight/Solution: Two Pointers, Dynamic Programming this problem is best solved by solving the brute force first, I had a hard time even coming up with this solution, but it is helpful to consider each column and how the height affects the trapped water we come to sense that the unit of water we measure at each column is min(l_max, r_max) - height[i] where l_max = max(height[0] ... height[i]) and r_max = max(height[i] ... height[n - 1]) so simply iterate from i = 0 to n and compute the l_max, r_max, and add min(l_max, r_max) - height[i] to a water variable the next obvious solution is to optimize the computations for l_max and r_max, we use dynamic programming to cache the maximum values and access them in constant space for the same brute force algorithm however, using two pointers will now solve the problem with O(1) space, but the idea is a little tricky to come across, what you'll have to observe is that the water unit we are dependent on is the min(l_max, r_max), so whenever l_max <= r_max, we first add our water amount by the respective pointer and move our left pointer and vice versa for right pointer this algorithm actually has a name, and it is the lower-envelope algorithm, it is best understood using a graph plot visualization if you want a formal explanation, the lower-envelope algorithm will provide that as well, recall from the graph plot for the example shown by ForAllEpsilon (on Youtube), the l_max function grows non-decreasingly and the r_max function grows non-increasingly, we then determine that if l_max is greater than r_max, r_max will eventually grow to where l_max is, so we move our r pointer, and vice versa
905. Sort By Array Parity Given an array A of non-negative integers, return an array consisting of all the even elements of A, followed by all the odd elements of A. You may return any answer array that satisfies this condition. Note: 1 <= A.length <= 5000 0 <= A[i] <= 5000
Insight/Solution: Two Pointers, Sort, Deque we can sort with a custom comparator (lambda x : x % 2) or we could build two separate lists (one for even, one for odd) and append the odd list to the even or we could do this in one pass using a deque, appendleft even nums, append odd nums first set i = 0 and j = end of A we can sort in place using two pointers and via quicksort (via the while loop method), where we check if A[i] % 2 == 1 and A[j] % 2 == 0, this means we need to swap when A[i] % 2 is even, we can move forward with our i pointer, if A[j] % 2 is odd, we can move forward with our j pointer
167. Two Sum II - Input array is sorted Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number. The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Note: Your returned answers (both index1 and index2) are not zero-based. You may assume that each input would have exactly one solution and you may not use the same element twice. Constraints: 2 <= nums.length <= 3 * 104 -1000 <= nums[i] <= 1000 nums is sorted in increasing order. -1000 <= target <= 1000
Insight/Solution: Two pointers, if nums[l] + nums[r] > total, then move r pointer, if <, move l pointer, else you found a match
417. Pacific Atlantic Water Flow Given an m x n matrix of non-negative integers representing the height of each unit cell in a continent, the "Pacific ocean" touches the left and top edges of the matrix and the "Atlantic ocean" touches the right and bottom edges. Water can only flow in four directions (up, down, left, or right) from a cell to another one with height equal or lower. Find the list of grid coordinates where water can flow to both the Pacific and Atlantic ocean. Note: The order of returned grid coordinates does not matter. Both m and n are less than 150.
Insight/Solution: BFS or DFS, for me, more intuitive to think of it as a DFS problem use two visited sets, one for the pacific ocean and the other for atlantic ocean, from each border of either ocean, conduct DFS and accordingly determine which coordinates are reachable for each ocean pick the intersections of the visited sets as our solution
286. Walls and Gates You are given a m x n 2D grid initialized with these three possible values. -1 - A wall or an obstacle. 0 - A gate. INF - Infinity means an empty room. We use the value 231 - 1 = 2147483647 to represent INF as you may assume that the distance to a gate is less than 2147483647. Fill each empty room with the distance to its nearest gate. If it is impossible to reach a gate, it should be filled with INF.
Insight/Solution: BFS problem, we put all the locations of our gates into the queue, and conduct BFS, if the new room we are on has a larger value than our current room, set the new room's value to the current room's + 1 and add the new location into our queue
111. Minimum Depth of Binary Tree Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. Note: A leaf is a node with no children.
Insight/Solution: DFS on a tree, similar to Maximum Depth of Binary Tree except we have extra conditions to check we have to check if a node has one child, that has to be dealt differently, for a clear idea, consider the degenerated linked list BT
310. Minimum Height Trees A tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any connected graph without simple cycles is a tree. Given a tree of n nodes labelled from 0 to n - 1, and an array of n - 1 edges where edges[i] = [ai, bi] indicates that there is an undirected edge between the two nodes ai and bi in the tree, you can choose any node of the tree as the root. When you select a node x as the root, the result tree has height h. Among all possible rooted trees, those with minimum height (i.e. min(h)) are called minimum height trees (MHTs). Return a list of all MHTs' root labels. You can return the answer in any order. The height of a rooted tree is the number of edges on the longest downward path between the root and a leaf. Constraints: 1 <= n <= 2 * 104 edges.length == n - 1 0 <= ai, bi < n ai != bi All the pairs (ai, bi) are distinct. The given input is guaranteed to be a tree and there will be no repeated edges.
Insight/Solution: BFS/Topological Sort, for this explanation, we will focus on BFS There are a few key points to note before solving the problem, since we are working with a tree, we know the graph is acyclic and fully connected, the height of a tree is the maximum length from a root to a leaf, also somewhat obscure, there is only one path to get from one node to another node now consider a piece of tissue, if you hold it on the corner, the height of your tissue is at its largest, but if we want the minimum height, we actually should hold our tissue at its centroid, and this analogy translates directly to this problem, we need to find the centroid nodes with the knowledge we got beforehand, we know that the can be at most 2 centroid nodes (use proof of contradiction for 3 centroid nodes), now the problem is a matter of finding centroid nodes of up to 2 use BFS in the manner that we remove the leaves of our graph, until we are left with 2 leaves (which will be the centroid nodes), to do this, initialize an adjacency list, hold leaves in a queue, then iterate through the leaves queue until it reaches <= 2 nodes, it is helpful to also have a new_leaves queue to keep track of both new leaves to store and the old leaves we are removing in our current iteration
37. Sudoku Solver Write a program to solve a Sudoku puzzle by filling the empty cells. A sudoku solution must satisfy all of the following rules: Each of the digits 1-9 must occur exactly once in each row. Each of the digits 1-9 must occur exactly once in each column. Each of the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid. The '.' character indicates empty cells. Constraints: board.length == 9 board[i].length == 9 board[i][j] is a digit or '.'. It is guaranteed that the input board has only one solution.
Insight/Solution: Backtracking + Hash table, the idea is pretty simple actually, the coding is pretty intensive all we really need to maintain is that we make a guess for an empty cell, then determine whether the row, column, or sub-grid is validated, if it can be valid, we recursively call the function to solve again, and whenever we finally can return True, it means we have successfully solved the board if it returns False though, that means our board is unsolvable, so we have to backtrack and make a new guess for which the guess we just had was invalid, we keep backtracking as we move along and will eventually reach a solution for our board the hash tables are used so that we optimize our look-up for row, column, and sub-grid validations to constant time, other data structures can be used such as a heap
51. N-Queens The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other. Given an integer n, return all distinct solutions to the n-queens puzzle. Each solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.' both indicate a queen and an empty space, respectively. Constraints: 1 <= n <= 9
Insight/Solution: Backtracking, honestly a very standard backtracking problem also similar to letter combinations of a phone number, etc The additional "fluff" that we have to account for is validating a queen, and this depends on our implementation, for simplicity, assume that we fill in a queen every row the idea of our backtracking implementation is to assume that our previously placed queens are valid, so we can save time by only considering if the queen we just placed is valid, since we go row by row, we can already dismiss checking if there is another queen in the same row, since that will never happen if our implementation is correct instead, we should consider the up-down case and diagonal cases, for up-down case, we just simply check if a queen is in the same column as our newly placed queen, as for diagonals, best if you draw it out, but the solution is abs(row - i) == abs(col - j), where row and col represents the newly placed queen's location
77. Combinations Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. You may return the answer in any order. Constraints: 1 <= n <= 20 1 <= k <= n
Insight/Solution: Backtracking, idea is similar to permutations, letter combinations of a phone number, all the classic backtracking problems
131. Palindrome Partitioning Given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of s. A palindrome string is a string that reads the same backward as forward.
Insight/Solution: Backtracking, really similar pattern to letter combinations of a phone number, permutations, and subsets
39. Combination Sum Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order. The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different. It is guaranteed that the number of unique combinations that sum up to target is less than 150 combinations for the given input. Constraints: 1 <= candidates.length <= 30 1 <= candidates[i] <= 200 All elements of candidates are distinct. 1 <= target <= 500
Insight/Solution: Backtracking, similar to the classic problems Note: a simple optimization is instead of checking if target < 0, check in the for loop if candidates[i] > target, then continue instead, that way, we never have to call backtrack again if our target went negative
784. Letter Case Permutation Given a string S, we can transform every letter individually to be lowercase or uppercase to create another string. Return a list of all possible strings we could create. You can return the output in any order. Constraints: S will be a string with length between 1 and 12. S will consist only of letters or digits.
Insight/Solution: Backtracking, similar to the other classic backtracking problems
1020. Number of Enclaves Given a 2D array A, each cell is 0 (representing sea) or 1 (representing land) A move consists of walking from one land square 4-directionally to another land square, or off the boundary of the grid. Return the number of land squares in the grid for which we cannot walk off the boundary of the grid in any number of moves. Note: 1 <= A.length <= 500 1 <= A[i].length <= 500 0 <= A[i][j] <= 1 All rows have the same size.
Insight/Solution: DFS on the edges of the grid, then count up the rest of the graph where the cell = 1 edge is either when i or j = 0 or the length of grid or length of grid[0]
33. Search in Rotated Sorted Array You are given an integer array nums sorted in ascending order, and an integer target. Suppose that nums is rotated at some pivot unknown to you beforehand (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]). If target is found in the array return its index, otherwise, return -1. Constraints: 1 <= nums.length <= 5000 -10^4 <= nums[i] <= 10^4 All values of nums are unique. nums is guranteed to be rotated at some pivot. -10^4 <= target <= 10^4
Insight/Solution: Binary Search, wow this one was hard it is essentially binary search with extra cases, we have to handle for cases when we perform a standard binary search and also modified searches, the modified searches depend on the way the array has been pivoted the best way to approach the problem is simply considering how a normal binary search would handle such portions of a rotated sorted array, the easiest way to think about it is to consider the values of nums[left] or nums[right] for instance, if we search for 6 in [4,5,6,7,0,1,2], we see that nums[m] = 7, and since it is greater than target, we may have to consider a standard binary search (left portion), but to verify, we have to check if it could be in the right portion which is simply checking if nums[l] <= nums[m] and nums[l] > target, that is in fact, the only way we could have the target be in the right portion if nums[m] > target follow the same logic for the other case when nums[m] < target, and thats it
153. Find Minimum in Rotated Sorted Array Suppose an array of length n sorted in ascending order is rotated between 1 and n times. For example, the array nums = [0,1,2,4,5,6,7] might become: [4,5,6,7,0,1,2] if it was rotated 4 times. [0,1,2,4,5,6,7] if it was rotated 7 times. Notice that rotating an array [a[0], a[1], a[2], ..., a[n-1]] 1 time results in the array [a[n-1], a[0], a[1], a[2], ..., a[n-2]]. Given the sorted rotated array nums, return the minimum element of this array. Constraints: n == nums.length 1 <= n <= 5000 -5000 <= nums[i] <= 5000 All the integers of nums are unique. nums is sorted and rotated between 1 and n times.
Insight/Solution: Binary search, first consider what binary search does: it takes the middle value and compares it with the key to determine which portion to consider next, for this special case, we know that our key is simply the smaller number of the mid, once we reach both our left and right pointers at an intersection, we have reached the smallest number of the array (loop invariant) a simple way to use this invariant is to consider the mid and right values if nums[m] > nums[r], then the minimum must be in the right portion, so we set l = m + 1 otherwise, then the minimum must be in the left portion, but it could also be the middle itself, so we set r = m we have to set l = m + 1 since we know for a fact that if nums[m] is greater than even 1 other value, it cannot be the minimum, but in the case where we set r = m, we cannot rule out that nums[m] is not the minimum
191. Number of 1 Bits Write a function that takes an unsigned integer and returns the number of '1' bits it has (also known as the Hamming weight). Note: Note that in some languages such as Java, there is no unsigned integer type. In this case, the input will be given as a signed integer type. It should not affect your implementation, as the integer's internal binary representation is the same, whether it is signed or unsigned. In Java, the compiler represents the signed integers using 2's complement notation. Therefore, in Example 3 above, the input represents the signed integer. -3. Follow up: If this function is called many times, how would you optimize it? Constraints: The input must be a binary string of length 32
Insight/Solution: Bit manipulation, here we want to use several parts of bit manipulation: &, <<, and >> operators since the binary string has a length of 32, we can simply iterate 32 times and check if the bit we are on is a 1 or 0, use a mask to move and check the next bit, moving from right to left (use << operator) Follow-up: the & will compare the least significant bits of the two numbers, since Hamming weight requires the number of 1 bits, we know that 1 & 1 will yield 1, so we can simply compare the least significant bit of n and 1 to calculate how many 1 bits we have, afterwards, we have to move our n so that we move onto the next bit, which we can do with >> do this until n reaches 0 Note: there is another way, it is a bit manipulation trick where we continuously increment the number of bits and do n &= (n - 1) instead, this flips all the 1s to 0s, so once we reach n = 0, we have our Hamming weight
53. Maximum Subarray Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.
Insight/Solution: can use either greedy or DP Greedy: locally solve the optimal solution and constantly compare it with the global solution DP: Kadane's Algorithm where f(i) = maximum subarray up to i f(1) = a1 f(i) = ai + max(0, f(i - 1)) Note: DP solution requires O(n) space if we cannot modify the input array
206. Reverse Linked List Reverse a singly linked list.
Insight/Solution: Can solve either iteratively or recursively Iteratively: initialize a curr pointer and prev pointer, iterate while curr is not null, continuously move curr.next to prev and prev to curr (will need a third pointer as well) recursively: idea is to work backwards once you reach the end of the linked list, remember to check for edge cases (when linked list size = 0, 1, 2)
17. Letter Combinations of a Phone Number Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order. A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.
Insight/Solution: Classic DFS/Backtracking problem, store all possible phone combinations in a hash table and run a backtracking algorithm to create all possible combinations the implementation I have has 5 parameters: backtrack(digits, combinations, letter_map, path, index)
329. Longest Increasing Path in a Matrix Given an integer matrix, find the length of the longest increasing path. From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).
Insight/Solution: DFS + Memoization Brute Force approach is simply conduct a DFS and find the longest path, but that would take O(4^(mn)), what we can do though is keep track of the longest path for each cell (memoize), and that would drastically optimize the runtime to O(mn)
886. Possible Bipartition Given a set of N people (numbered 1, 2, ..., N), we would like to split everyone into two groups of any size. Each person may dislike some other people, and they should not go into the same group. Formally, if dislikes[i] = [a, b], it means it is not allowed to put the people numbered a and b into the same group. Return true if and only if it is possible to split everyone into two groups in this way.
Insight/Solution: DFS + coloring, the graph we are trying to confirm is a bipartite graph simply DFS each vertex in the graph, but instead of using our visited label as ether 0 or 1, we label each vertex with a 0 for unvisited, 1 for group A, and -1 for group B, whenever adjacent vertices are in the same group, we cannot have a bipartite graph another case to consider is if we are still exploring our depth, then you should recursively call DFS but with the other group instead (simply multiply group by -1), if all else passes without any "collisions", we confirm that the graph is not bipartite once we traverse through all the connected components, we can determine that our graph is bipartite
104. Maximum Depth of Binary Tree Given the root of a binary tree, return its maximum depth. A binary tree's maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.
Insight/Solution: DFS on a tree Base case: An empty tree is essentially null, which would have a depth of 0. Outside of the base case, we would already have at least a depth of 1. Therefore, we can recursively solve the depth as: 1 + max(depth of left, depth of right)
130. Surrounded Regions Given a 2D board containing 'X' and 'O' (the letter O), capture all regions surrounded by 'X'. A region is captured by flipping all 'O's into 'X's in that surrounded region. Explanation: Surrounded regions shouldn't be on the border, which means that any 'O' on the border of the board are not flipped to 'X'. Any 'O' that is not on the border and it is not connected to an 'O' on the border will be flipped to 'X'. Two cells are connected if they are adjacent cells connected horizontally or vertically.
Insight/Solution: DFS or BFS and coloring, the idea is to understand when will there not be a flip, turns out that if an 'O' starts at a border, it will be able to escape conduct DFS or BFS at those spots and color them as 'E' (for escaped), afterwards, iterate through the entire board, and if board[i][j] == 'E', flip it to 'O', otherwise if it is 'O', flip it to 'X'
494. Target Sum You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol. Find out how many ways to assign symbols to make sum of integers equal to target S. Constraints: The length of the given array is positive and will not exceed 20. The sum of elements in the given array will not exceed 1000. Your output answer is guaranteed to be fitted in a 32-bit integer.
Insight/Solution: DFS or DP problem, do yourself a favor and implement it using memoization instead of tabulation pretty easy to consider it as a DFS problem, but it will TLE O(2^n) instead, consider the problem like a DP knapsack 0/1 problem where we add up the number of ways every time, the memoization is super easy to add afterwards I repeat, use memoization. This problem killed my interest for solving DP problems using bottom-up... unless its like coin change or something
200. Number of Islands Given an m x n 2d grid map of '1's (land) and '0's (water), return the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. Constraints: m == grid.length n == grid[i].length 1 <= m, n <= 300 grid[i][j] is '0' or '1'.
Insight/Solution: DFS, we traverse through the grid, and if we hit grid[i][j] = '1', we increment our count by 1 and perform a DFS to change all the land connected to this spot with '*' (marking it as seen), that way, we can keep track of the number of islands we have
133. Clone Graph Given a reference of a node in a connected undirected graph. Return a deep copy (clone) of the graph. Each node in the graph contains a val (int) and a list (List[Node]) of its neighbors. class Node { public int val; public List<Node> neighbors; } Test case format: For simplicity sake, each node's value is the same as the node's index (1-indexed). For example, the first node with val = 1, the second node with val = 2, and so on. The graph is represented in the test case using an adjacency list. Adjacency list is a collection of unordered lists used to represent a finite graph. Each list describes the set of neighbors of a node in the graph. The given node will always be the first node with val = 1. You must return the copy of the given node as a reference to the cloned graph. Constraints: 1 <= Node.val <= 100 Node.val is unique for each node. Number of Nodes will not exceed 100. There is no repeated edges and no self-loops in the graph. The Graph is connected and all nodes can be visited starting from the given node.
Insight/Solution: DFS/BFS + Hash Table, use the hash table to store the deep copies of each node, use DFS/BFS to traverse through the original node to make these copies
1414. Find the Minimum Number of Fibonacci Numbers Whose Sum Is K Given an integer k, return the minimum number of Fibonacci numbers whose sum is equal to k. The same Fibonacci number can be used multiple times. The Fibonacci numbers are defined as: F1 = 1 F2 = 1 Fn = Fn-1 + Fn-2 for n > 2. It is guaranteed that for the given constraints we can always find such Fibonacci numbers that sum up to k. Constraints: 1 <= k <= 10^9
Insight/Solution: DP + greedy, first compute the fibonacci sequence using a DP table and stop right before the next fibonacci number becomes greater than k Now iterate, in reverse, until k reaches 0, at each iteration, we first check if k - dp[i] we are on yields a number >= 0, and if it does, we subtract dp[i] from k and keep iterating the greedy choice is picking the maximum fibonacci number that we can subtract from, recognize that if we subtract the largest value, we can effectively minimize the number of fibonacci numbers to use for our sum complexity is O(lgk)
547. Friend Circles There are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. For example, if A is a direct friend of B, and B is a direct friend of C, then A is an indirect friend of C. And we defined a friend circle is a group of students who are direct or indirect friends. Given a N*N matrix M representing the friend relationship between students in the class. If M[i][j] = 1, then the ith and jth students are direct friends with each other, otherwise not. And you have to output the total number of friend circles among all the students.
Insight/Solution: DFS/BFS, number of components in an undirected graph for, this time, an adjacency matrix, very similar idea as #323 on LeetCode iterate through each vertex in the graph, and conduct a DFS if we have not seen the vertex yet, keep a counter for friend circles Note: I struggled with this problem a lot due to a very stupid error, "for j in M[i]" and "for j in range(len(M[i]))" are NOT interchangeable, the former will give us only the actual cell values, so we would only visit vertices 0 and 1 if we had used the former
323. Number of Connected Components in an Undirected Graph Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), write a function to find the number of connected components in an undirected graph. Note: You can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0, 1] is the same as [1, 0] and thus will not appear together in edges.
Insight/Solution: DFS/BFS, problem is essentially number of islands but using adjacency lists, since we are given only the edges, we have to build our adjacency list (use a hash table of lists), don't forget that it is undirected, so we have to add (v, u) in addition to (u, v) Keep track of our visited nodes in a boolean array and perform DFS, in our main loop, keep track of how many times we perform DFS, which will be the answer for the number of connected components in our graph
46. Permutations Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order. Constraints: 1 <= nums.length <= 6 -10 <= nums[i] <= 10 All the integers of nums are unique.
Insight/Solution: DFS/Backtracking problem, for every number in nums, we branch out to continue forming a permutation without including the number we just added we can use either a set to keep track of which element we've already used or pass in our nums without the element we just added: nums[:i] + nums[i + 1:] if we just added nums[i] to our path
22. Generate Parentheses Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
Insight/Solution: DFS/Backtracking problem, we know that our combination must have a length of 2 * n since every pair has 2 characters for the opening and closing parentheses We have to make a choice whether to add '(' or ')' to our path, always start with '(' and keep count that we can only have n number of each parenthesis, helpful to use a left count and right count for the open and closed parentheses
5. Longest Palindromic Substring Given a string s, return the longest palindromic substring in s. Constraints: 1 <= s.length <= 1000 s consist of only digits and English letters (lower-case and/or upper-case),
Insight/Solution: DP problem, build an N x N matrix where N = s.length, there are several cases to consider: dp[i][j] = { T, if i == j T, if i == j - 1 and s[i] == s[j] T, if dp[i + 1][j - 1] and s[i] == s[j] F, otherwise essentially, our 2D matrix will start with a diagonal of T, and we don't consider the left bottom portion of the matrix (due to symmetry), consider the columns as pointers to the end of a substring and the rows as the beginning pointers, so we should traverse in column major order, keep track of a substring when we satisfy a T condition of our recursive expression
63. Unique Paths II A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below). Now consider if some obstacles are added to the grids. How many unique paths would there be? An obstacle and space is marked as 1 and 0 respectively in the grid. Constraints: m == obstacleGrid.length n == obstacleGrid[i].length 1 <= m, n <= 100 obstacleGrid[i][j] is 0 or 1.
Insight/Solution: DP problem, continuation of 62. Unique Paths dp[0][0] = { 0, if s[0][0] == 1 1, if s[0][0] == 0 dp[i][0] = { 0, if s[i][0] == 1 or dp[i - 1][0] == 0 1, otherwise ^ same with dp[0][j] dp[i][j] = { 0, if s[i][j] == 0 dp[i - 1][j] + dp[i][j - 1], otherwise if there is an obstacle in the way from our starting row or column, we cannot continue into those paths since we can only go right or down, which is why we have the conditions dp[i][0] and dp[0][j]
322. Coin Change You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1. You may assume that you have an infinite number of each kind of coin.
Insight/Solution: DP problem, here is the recurrence relation: 0, if amount = 0 inf or amount + 1, if amount < 0 min of 1 <= k <= K (1 + coinChange(amount - coin)), otherwise; coin is one of the denominations in coin Essentially, we break down the number of coins we need with the type of coins we have, so if we have to make change for 6 with coins [1, 2, 5], we can determine that by 1 + number of ways to get 6 - 1, 6 - 2, and 6 - 5 Edge cases: if we can't get a certain amount, if our amount is 0
518. Coin Change 2 You are given coins of different denominations and a total amount of money. Write a function to compute the number of combinations that make up that amount. You may assume that you have infinite number of each kind of coin.
Insight/Solution: DP problem, here is the recurrence relation: 1, if amount = 0 dp[i] + dp[i - coin], if coin >= i where we also initialize dp[i] = 0 the idea is to calculate the number of ways we get all amounts from 1 to amount each coin at a time, similarly with coin change, we can store our values in the manner [i - coin], since the number of ways we find a certain value depends what coin we have as well, we can add each time since for all the number of ways we can get dp[i - coin], we just have to throw in the coin value, so we keep building up to the amount of ways
300. Longest Increasing Subsequence Given an integer array nums, return the length of the longest strictly increasing subsequence. A subsequence is a sequence that can be derived from an array by deleting some or no elements without changing the order of the remaining elements. For example, [3,6,2,7] is a subsequence of the array [0,3,1,6,2,2,7]. Follow up: Could you come up with the O(n2) solution? Could you improve it to O(n log(n)) time complexity?
Insight/Solution: DP problem, our subproblem is to find the longest subsequence without the value are currently on, in other words: dp[i] = max(dp[j])+1, ∀0≤j<i and num[i] > num[j] LIS_length = max(dp[i]), ∀0≤i<n Follow-up: the idea is that we can build another array called tails which holds the following invariant: (1) if x is larger than all tails, append it, increase the size by 1 (2) if tails[i-1] < x <= tails[i], update tails[i] also partially part of patience sort, we use binary search to find elements efficiently, note that since this algorithm is part of patience sort, we will only have the length of our subsequence correct, not the actual ordering of the subsequence (unless you use parent arrays and such modifications)
1143. Longest Common Subsequence Given two strings text1 and text2, return the length of their longest common subsequence. A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters. (eg, "ace" is a subsequence of "abcde" while "aec" is not). A common subsequence of two strings is a subsequence that is common to both strings. If there is no common subsequence, return 0. Constraints: 1 <= text1.length <= 1000 1 <= text2.length <= 1000 The input strings consist of lowercase English characters only.
Insight/Solution: DP problem, personally think it is easiest to explain using a recursion tree here is the recursive expression: f(text1, text2) = { 0, if text1 == '' or text2 == '' 1 + f(text1[1:], text2[1:]), if text1[0] == text2[0] max(f(text1[1:], text2), f(text1, text2[1:])), otherwise the idea is that we do not have a subsequence if one of the strings is empty if the first character of both strings match, then we tally up that character and continue solving the subsequence without the first characters otherwise, we could have a subsequence either when one of the strings is a subsequence, and since we want the longest common subsequence, we should take the max of both sides
10. Regular Expression Matching Given an input string (s) and a pattern (p), implement regular expression matching with support for '.' and '*' where: '.' Matches any single character. '*' Matches zero or more of the preceding element. The matching should cover the entire input string (not partial). Constraints: 0 <= s.length <= 20 0 <= p.length <= 30 s contains only lowercase English letters. p contains only lowercase English letters, '.', and '*'. It is guaranteed for each appearance of the character '*', there will be a previous valid character to match.
Insight/Solution: DP problem, pretty complicated but when broken down accordingly, the problem becomes intuitive first dive only into a case using '.' (not '*' yet), this is pretty simple, the recursive expression would be: T, if s[i] == p[j] or p[j] == '.' F, otherwise now we consider this '*' case, recognize that we must have a preceding letter before '*' so there must also be some length condition to check, but as for how it works, the '*' can do two things: negate all of the preceding letter's frequency or keep counting s[i], let's represent them as case *1 and case *2, note that case *2 requires s[i] == p[j] to keep track of the pair of preceding letter and '*', we consider p[j] = preceding letter and p[j + 1] = '*' therefore, our new recursive expression is now if p[j + 1] == '*', return f(s, p[j + 2:]) or (s[i + 1:], p[j + 1:]) else, consider the two cases with only '.' note: the recursive expression is not the only condition we have to check, we also have to ensure '*' comes after a preceding letter (determine via length condition), the only condition we do not care if s[i] == p[j] is for case *1 suggestion: solve using recursion first (main algorithm code in a helper function) and add memoization to the code, extremely simple and intuitive flow of the logic with this explanation compared to the bottom-up approach
101. Symmetric Tree Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center). Follow up: Solve it both recursively and iteratively.
Insight/Solution: a tree is symmetric if left.left == right.right and left.right = right.left, if one of them is null, it is not symmetric, if both are null, it could be symmetric (final condition is to check if the values match) Recursively: pretty simple, just use a helper function where the top call is: isMirror(root, root) Iteratively: similar to BFS, use a queue and follow what we accomplish with the recursive solution
213. House Robber II You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and it will automatically contact the police if two adjacent houses were broken into on the same night. Given a list of non-negative integers nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police. Constraints: 1 <= nums.length <= 100 0 <= nums[i] <= 1000
Insight/Solution: DP problem, recognize that there can only be two solutions: either the total amount of money robbed includes the first house but excludes the last house or includes the last house but excludes the first house, whichever total sum is higher is the maximum amount of money we can rob knowing this, we can degenerate this problem into House Robber I where we have inputs nums[1:] and nums[:-1], then take the max of both of them Note: use a helper function to program the House Robber I function
377. Combination Sum IV Given an integer array with all positive numbers and no duplicates, find the number of possible combinations that add up to a positive integer target. Follow up: What if negative numbers are allowed in the given array?How does it change the problem? What limitation we need to add to the question to allow negative numbers?
Insight/Solution: DP problem, should also really be called permutation sum since order of the "combination" does matter, meaning... (1,1,2) is essentially the same as (1,2,1) or (2,1,1) the true combination sum problem is actually 518. Coin Change 2, and the different between the two algorithms is how the loop is nested, and it's easy to see why if the outer loop contains our nums, then the problem is a combination sum problem since we would only calculate the number of combinations we could have for aa certain value once, for the case where the inner loop contains nums, we consider multiple ways to getting to a value as unique from others, so we end up solving permutations instead of combinations Follow-up: with negative numbers, we would end up having an infinite amount of ways to essentially get to 0, so we would need some kind of maximum length limitation to restrict the number of "combinations"
673. Number of Longest Increasing Subsequence Given an integer array nums, return the number of longest increasing subsequences. Notice that the sequence has to be strictly increasing. Constraints: 1 <= nums.length <= 2000 -106 <= nums[i] <= 106
Insight/Solution: DP problem, similar to Longest Increasing Subsequence except we have an extra array to keep track of the number of longest increasing subsequences we do have to modify the inner part of the algorithm, we have to keep track of when we meet a new longest increasing subsequence or if we meet different longest increasing subsequence if length[i] == length[j], this implies that we have an even longer increasing subsequence, so we should update the length and also set count[i] = count[j] (keeps track of all the increasing subsequences), we can do this since we know for sure that nums[i] > nums[j], where j is the pointer that moves around and i is the pointer that points to the end of a subarray if length[i] == length[j] + 1, we know that the subsequence up to j is representing a different increasing subsequence which will also upgrade to be one of the longest increasing subsequences, so we simply do count[i] += count[j]
543. Diameter of a Binary Tree Given a binary tree, you need to compute the length of the diameter of the tree. The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root. Note: The length of path between two nodes is represented by the number of edges between them.
Insight/Solution: The idea is to use DFS which will bring the diameter to us as a side effect We calculate the depth of a tree, where we actually start with a depth of -1 for a solo tree node. Build up the left and right subtree depths + 1, sum them together for our diameter. However, we should return the max of depths for left and right, since the diameter encompasses both paths, while we can only account for one full path when returning.
647. Palindromic Substrings Given a string, your task is to count how many palindromic substrings in this string. The substrings with different start indexes or end indexes are counted as different substrings even they consist of same characters.
Insight/Solution: DP problem, very similar to 5. Longest Palindromic Substring, in fact, the solution is exactly the same except we update our counter of amount of substrings instead of update the longest palindromic substring the recursive expression: dp[i][i] = True dp[i][i + 1] = True dp[i][j] = True, if dp[i + 1][j - 1] and s[i] == s[j] dp[i][j] = False, otherwise there is another solution using two pointers (expand from the center), think of the palindrome as an onion, as you peel its ends, what you have left is just a smaller onion, so if you work towards your center, you keep having smaller and smaller onions so we can start from the center of the substring and make our way towards the ends, keep counting as we form palindromes note that we have two types of palindromes, depending on their lengths: odd-lengths have a center size of 1 character and even-lengths have a center pair of 2 characters, so we can check both each time through a helper function
139. Word Break Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
Insight/Solution: DP problem, we can break up the problem in subproblems by considering the string when they have less characters, perhaps easiest explained using a recursive expression: True, if considering an empty string True, if dp[j] = True and s[j:i] is a valid word False, otherwise dp[j] has to be True since that's how we know that we're maintaining a valid word break
704. Binary Search Given a sorted (in ascending order) integer array nums of n elements and a target value, write a function to search target in nums. If target exists, then return its index, otherwise return -1. Note: You may assume that all elements in nums are unique. n will be in the range [1, 10000]. The value of each element in nums will be in the range [-9999, 9999].
Insight/Solution: binary search
49. Group Anagrams Given an array of strings strs, group the anagrams together. You can return the answer in any order. An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once. Constraints: 1 <= strs.length <= 104 0 <= strs[i].length <= 100 strs[i] consists of lower-case English letters.
Insight/Solution: Hash table (hashing), we can build our own hashing function to store words that are anagrams of each other, then simply return the values of the hash table as a list the key is how we will construct our hash function, we could sort the words and the sorted word is stored as a key, but we can actually do much better by counting the number of letters we see in the word we know that the letters are only lower case English letters, so we could build a fixed array of size 26 where each index represents a letter, so then you can count up the number of each letters and returning a tuple of the end result (summing them up fails, as that just means words of same length will be considered into the same hash key) simply use your newly constructed hash function as a hash key with a hash table, and return the values afterwards as a list
452. Minimum Number of Arrows to Burst Balloons There are some spherical balloons spread in two-dimensional space. For each balloon, provided input is the start and end coordinates of the horizontal diameter. Since it's horizontal, y-coordinates don't matter, and hence the x-coordinates of start and end of the diameter suffice. The start is always smaller than the end. An arrow can be shot up exactly vertically from different points along the x-axis. A balloon with xstart and xend bursts by an arrow shot at x if xstart ≤ x ≤ xend. There is no limit to the number of arrows that can be shot. An arrow once shot keeps traveling up infinitely. Given an array points where points[i] = [xstart, xend], return the minimum number of arrows that must be shot to burst all balloons. Constraints: 0 <= points.length <= 104 points[i].length == 2 -231 <= xstart < xend <= 231 - 1
Insight/Solution: Greedy algorithm, also very similar to the activity selection problem sort the points in ascending order with respect to the x_end, then note that we'll need at least 1 arrow if there is at least 1 balloon, so initialize our arrows = 1 and last_point = sorted_points[0][1], then check if our last_point < sorted_points[i][0] and if it is, we need another arrow (since that means the current arrow we had would not shoot through the balloon at i) you can use a comparator to sort the points, but you can also use a lambda function in Python lambda x: x[1] you can also sort by x[0], but that means you'll have to modify the algorithm going in reverse order
142. Linked List Cycle II Given a linked list, return the node where the cycle begins. If there is no cycle, return null. There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to. Note that pos is not passed as a parameter. Notice that you should not modify the linked list. Constraints: The number of the nodes in the list is in the range [0, 104]. -105 <= Node.val <= 105 pos is -1 or a valid index in the linked-list. Follow up: Can you solve it using O(1) (i.e. constant) memory?
Insight/Solution: Hash Set and Floyd's Tortoise and Hare Algorithm use a hash set to store all the seen nodes, if a node has already been seen, return it since that node must be the start of the cycle, if the pointer ever points to None, there was no cycle Follow-up: Similar to 287 (Find the Duplicate Number) except the problem is applied to a linked list, simply have the tortoise and hare pointers and iterate until they are equal, this marks the intersection, the proof of the algorithm suggests that the distance from the beginning to the cycle equals the distance from the intersection to the cycle, so reset one of the pointers to the head and move each pointer one by one until they are equal
554. Brick Wall There is a brick wall in front of you. The wall is rectangular and has several rows of bricks. The bricks have the same height but different width. You want to draw a vertical line from the top to the bottom and cross the least bricks. The brick wall is represented by a list of rows. Each row is a list of integers representing the width of each brick in this row from left to right. If your line go through the edge of a brick, then the brick is not considered as crossed. You need to find out how to draw the line to cross the least bricks and return the number of crossed bricks. You cannot draw a line just along one of the two vertical edges of the wall, in which case the line will obviously cross no bricks. Note: The width sum of bricks in different rows are the same and won't exceed INT_MAX. The number of bricks in each row is in range [1,10,000]. The height of wall is in range [1,10,000]. Total number of bricks of the wall won't exceed 20,000.
Insight/Solution: Hash Table, recognize that for each row, we can check which area has an edge if we can sum up to that edge's value, this means we can calculate the number of edges we find and subtract them from the total number of rows, then we'll have the number of bricks we have to pass through, simply take the minimum out of all of them and we have our answer however, we do have an edge case, which is when each row has the same number of bricks with the same width, this means we have to go through all the bricks therefore, we should update min_bricks as min(min_bricks, # of bricks - table[i]), and we have to make sure we do not count the last brick while tallying up our hash table since all the bricks at the end will have edges lined up, which violates the constraint in the description
36. Valid Sudoku Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules: Each row must contain the digits 1-9 without repetition. Each column must contain the digits 1-9 without repetition. Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition. Note: A Sudoku board (partially filled) could be valid but is not necessarily solvable. Only the filled cells need to be validated according to the mentioned rules. Constraints: board.length == 9 board[i].length == 9 board[i][j] is a digit or '.'.
Insight/Solution: Hash set, and while not algorithmic, clean style of code to reiterate, we have three conditions to fulfill if we want to validate an initial sudoku board, and we should handle each condition with its own helper function, this will make the main loop look extremely simple, and in general, the code is very easy to read and follow along there really is no algorithmic thinking here, all you have to do is check if the value was already seen or not using a hash set, I would go for code organization if asked this question, the question is as simple as iterating through each element in a given row or column and checking if the value has already been seen for the nine 3 x 3 sub-box grids, iterate accordingly with appropriate start and end limits, start: 3i and end: 3(i + 1), for i = 0 to 3 since the Sudoku board never changes in size, everything is constant O(1)
121. Best Time to Buy and Sell Stock Say you have an array for which the ith element is the price of a given stock on day i. If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit. Note that you cannot sell a stock before you buy one.
Insight/Solution: Iterate through the list and if we reach a smaller price than our min_price, change our min_price; else if prices[i] - min_price is greater than max_profit, update max_profit
448. Find All Numbers Disappeared in an Array Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once. Find all the elements of [1, n] inclusive that do not appear in this array. Could you do it without extra space and in O(n) runtime? You may assume the returned list does not count as extra space.
Insight/Solution: Key here is to use every single information in this question. Use the numbers in our array as indices, and multiply the value in that index by -1 (unless it was already changed to negative, keep it as negative). Iterate once more from 0 to n - 1, and if nums[i] > 0, we know that i + 1 was a number that disappeared.
1046. Last Stone Weight We have a collection of stones, each stone has a positive integer weight. Each turn, we choose the two heaviest stones and smash them together. Suppose the stones have weights x and y with x <= y. The result of this smash is: If x == y, both stones are totally destroyed; If x != y, the stone of weight x is totally destroyed, and the stone of weight y has new weight y-x. At the end, there is at most 1 stone left. Return the weight of this stone (or 0 if there are no stones left.) Note: 1 <= stones.length <= 30 1 <= stones[i] <= 1000
Insight/Solution: Max-heap, take the two largest stones, smash them together, and if there is still a leftover stone, put it back into the max-heap Continue to do this until the size of our heap reaches less than size 2, and return whatever stone is leftover if there was 1 stone left, other wise return 0 Note: Python's heap library uses min-heap implementation, we can easily convert it into a max-heap by simply multiplying the values of our stones by -1
160. Intersection of Two Linked Lists Write a program to find the node at which the intersection of two singly linked lists begins. Notes: If the two linked lists have no intersection at all, return null. The linked lists must retain their original structure after the function returns. You may assume there are no cycles anywhere in the entire linked structure. Each value on each linked list is in the range [1, 10^9]. Your code should preferably run in O(n) time and use only O(1) memory.
Insight/Solution: Recognize the commutative property of addition: a + b = b + a = c, so consider pointers headA and headB if headA becomes null, reset to headB, same with headB. headA and headB eventually will reach c together, since if headA reached null before headB, it must go through the same path as headB. the residual will alter headA and headB to mark on same levels. if both end up becoming null together, we know there was no intersection.
654. Maximum Binary Tree You are given an integer array nums with no duplicates. A maximum binary tree can be built recursively from nums using the following algorithm: Create a root node whose value is the maximum value in nums. Recursively build the left subtree on the subarray prefix to the left of the maximum value. Recursively build the right subtree on the subarray suffix to the right of the maximum value. Return the maximum binary tree built from nums. Constraints: 1 <= nums.length <= 1000 0 <= nums[i] <= 1000 All integers in nums are unique.
Insight/Solution: Recursion + DFS or Stack, the problem is easily presentable as a recursive DFS problem, just continue to build these maximum binary trees recursively like so: if l == r return None max_index, max_value = compute_max(nums, l, r) root = TreeNode(max_value) root.left = f(nums, l, max_index) root.right = f(nums, max_index + 1, r) return root # O(n^2) due to computing the max n times using the stack is much trickier but takes O(n) there are two rules for whether we make the node a left child or a right child: 1. suppose we have a current node, and its value is greater than the top of the stack's node's value, then we can pop the stack and the last popped node is the left child of the current node 2. after popping, if there still is a node in the stack, then the current node is a candidate of the top of the stack's right child really tricky... hope to understand it more
112. Path Sum Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum. Note: A leaf is a node with no children.
Insight/Solution: Recursion + DFS, break down the tree into subtrees while also subtracting the sum if root is empty, return False if root.val == sum and root is a leaf, return True return f(root.left, sum - root.val) or f(root.right, sum - root.val)
235. Lowest Common Ancestor of a Binary Search Tree Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST. According to the definition of LCA on Wikipedia: "The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself)." Constraints: The number of nodes in the tree is in the range [2, 105]. -109 <= Node.val <= 109 All Node.val are unique. p != q p and q will exist in the BST.
Insight/Solution: Recursion + DFS, convince yourself that the LCA is the instant root.val falls in [p.val, q.val] or [q.val, p.val], it is, so the recursive expression is as follows: if p.val < root.val and q.val < root.val, return LCA(root.left, p, q) elif p.val > root.val and q.val > root.val, return LCA(root.right, p, q) return root Note: the root can also be a descendant of itself, meaning it can also be p or q, that is why we don't include <= or >= in the conditions
572. Subtree of Another Tree Given two non-empty binary trees s and t, check whether tree t has exactly the same structure and node values with a subtree of s. A subtree of s is a tree consists of a node in s and all of this node's descendants. The tree s could also be considered as a subtree of itself.
Insight/Solution: Recursion + DFS, first, travel down the bigger tree via standard dfs, if we find node equal to the value of root of the smaller tree, compare the subtrees. We travel down both subtrees at the same time and if and only if every node is the same then we know we have found the right subtree.
230. Kth Smallest Element in a BST Given a binary search tree, write a function kthSmallest to find the kth smallest element in it. Follow up: What if the BST is modified (insert/delete operations) often and you need to find the kth smallest frequently? How would you optimize the kthSmallest routine? Constraints: The number of elements of the BST is between 1 to 10^4. You may assume k is always valid, 1 ≤ k ≤ BST's total elements.
Insight/Solution: Recursion + DFS, same idea as 98. Validate Binary Search Tree, do an inorder traversal but this time, keep decrementing k until 0, and return the node's value once k = 0 Follow-up: this modification we have to make matches a description of a database, a solution is to do something like an LRU cache and store a doubly linked list as well, something where a node of the BST points to its corresponding node to a doubly linked list which is used for searching this follow-up is more of a design problem, another advanced data structure (which is also used in database managements) is the B+ tree
236. Lowest Common Ancestor of a Binary Tree Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According to the definition of LCA on Wikipedia: "The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself)." Constraints: The number of nodes in the tree is in the range [2, 105]. -109 <= Node.val <= 109 All Node.val are unique. p != q p and q will exist in the tree.
Insight/Solution: Recursion + DFS, similar to 235 which uses BSTs, except we do not have the special property that BST has so instead, let's take a closer look at the trees, the LCA is the node that is deepest compared to the other CAs, but what does that mean in terms of recursion? it is the first node that joins p and q from different sides, since recursion works such that we go deep, then bubble back up, so if we bubble back up to a node that satisfies the CA condition, we guarantee it is also the LCA the edge happens when one of the descendants is its own ancestor, which we handle that if we find a left node but not a right node (or right node but not a left node), we assume the other node must be in the left subtree or right subtree (whichever was returned), this is because our constraint says p and q exist in the tree, so we would rule that the node we haven't found yet is part of the subtree we would return this question is a logically thinking based problem I would say
100. Same Tree Given the roots of two binary trees p and q, write a function to check if they are the same or not. Two binary trees are considered the same if they are structurally identical, and the nodes have the same value. Constraints: The number of nodes in both trees is in the range [0, 100]. -104 <= Node.val <= 104
Insight/Solution: Recursion + DFS, the only way a tree is the same is if both are empty or both have the same value, and they both must have the same tree structure any other time means it is false, so we could set up our recursion as follows if p and q are both empty, return True if only one of them is empty, return False if p.val != q.val, return False once you check all these conditions, return f(p.left, q.left) and f(p.right, q.right)
19. Remove Nth Node From End of List Given the head of a linked list, remove the nth node from the end of the list and return its head. Follow up: Could you do this in one pass?
Insight/Solution: Sentinel Node + two pointers, the question presents itself well as a puzzle, but the edge cases are very difficult to handle unless a sentinel node is used A two-pass algorithm consists of determining the length of the linked list first, then subtracting the length by n, then iterating until the length reaches 0 with another pointer at head, every iteration, we decrement length by 1, once length reaches 0, we simply have our pointer's next point to pointer.next.next Follow-up: Notice that the nth node from the end and the end of the linked list (which we will consider as the null pointer after the last valid node) has a gap of n + 1, so initially, we could iterate only our fast pointer while incrementing a variable gap which is initially set to 0, once gap reaches n + 1, we can start moving our slow pointer, once our fast pointer reaches null, our slow pointer is at the node right before the nth node from the end, so we can simply do slow.next = slow.next.next altogether, we return sentinel.next which handles the edge cases very neatly for us
2. Add Two Numbers You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list. You may assume the two numbers do not contain any leading zero, except the number 0 itself. Constraints: The number of nodes in each linked list is in the range [1, 100]. 0 <= Node.val <= 9 It is guaranteed that the list represents a number that does not have leading zeros.
Insight/Solution: Sentinel node, this problem is really an issue with edge cases start off with a sentinel node, then keep building the linked list with the sum of both linked list values and a carry, the carry should be set to 1 if the original sum >= 10 otherwise 0, and when we construct the linked list node, we should set the value as total sum (including the carry) % 10, make sure to account for when a linked list is longer than the other, and if the carry ends up pushing to the absolute end of the linked list an example of such edge cases: 2->9 7->8->4 or 2->9 7->8->9
617. Merge Two Binary Trees Given two binary trees and imagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not. You need to merge them into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of new tree. Note: The merging process must start from the root nodes of both trees.
Insight/Solution: Simply perform a DFS that updates a third tree with added values if either node is null, return the non-null node initialize t3 with t1.val + t2.val set t3.left = merge(t1.left, t2.left) set t3.right = merge(t1.right, t2.right)
3. Longest Substring Without Repeating Characters Given a string, find the length of the longest substring without repeating characters. Constraints: 0 <= s.length <= 5 * 104 s consists of English letters, digits, symbols and spaces.
Insight/Solution: Sliding Window + Hash Table, idea is to keep track of which characters we have seen in the hash table, forming a key value pair: {character: index}, keep a starting pointer initially at 0 and length = 0 as we iterate, we check if the character we are on has been seen or not, if it has, we have to also check if the character is actually within our window (which is why we have our value as the index in our key-value pair), so that way, we can check if the character that has been seen is taking into consideration our current window, not a previous window if it is in our current window, we simply move our starting pointer to table[seen character] + 1 (indicating a fresh new window) as we iterate, we always add/update the current character's index in our key-value pair, and update length to be the maximum length it can be
424. Longest Repeating Character Replacement Given a string s that consists of only uppercase English letters, you can perform at most k operations on that string. In one operation, you can choose any character of the string and change it to any other uppercase English character. Find the length of the longest sub-string containing all repeating letters you can get after performing the above operations. Note: Both the string's length and k will not exceed 104.
Insight/Solution: Sliding Window + Hash Table, the idea is that our substring should contain the letter with the highest frequency since that will maximize our length with our k operations, this is why we use a hash table (or a counter), then we should also consider the case when should we move our left pointer the sliding window case should happen when we know we ran out of k operations, so we move our left pointer to the right by one, however, we also have to keep track of our letter counts, so we will also decrement count[s[i]] by 1, when i = left pointer the condition to check for this sliding window case is if our current window length is greater than the maximum frequency of a character + k, so we should also keep another variable to track the maximum frequency as we iterate throughout the string after we reach the end of our string, i will represent the start of our substring, so we can simply return the length of the string - i to get the length of our maximum substring
159. Longest Substring with At Most Two Distinct Characters Given a string s , find the length of the longest substring t that contains at most 2 distinct characters.
Insight/Solution: Sliding Window + Hash Table, this problem I think is more straightforward than the one with repeating characters, essentially though, you want to keep track of the length of the hash table, once it reaches over 2, we have to find the key-value pair with the smallest index location, remove it, and update our opening window pointer at every iteration, we constantly update the hash table value as we only care about the most recently added letters, and thus we also should constantly update our max_length
209. Minimum Size Subarray Sum Given an array of n positive integers and a positive integer s, find the minimal length of a contiguous subarray of which the sum ≥ s. If there isn't one, return 0 instead. Follow up: If you have figured out the O(n) solution, try coding another solution of which the time complexity is O(n log n).
Insight/Solution: Sliding Window technique, use two pointers to point at the ends of our window, move our end pointer if total_sum < s, otherwise update our min_length, subtract total_sum by nums[start], and move our start pointer, we continuously iterate until our end pointer reaches the end the key behind this algorithm is to not move our two pointers back, only keep them moving forward Follow-up: Using binary search, look into in the future
904. Fruit Into Baskets In a row of trees, the i-th tree produces fruit with type tree[i]. You start at any tree of your choice, then repeatedly perform the following steps: Add one piece of fruit from this tree to your baskets. If you cannot, stop. Move to the next tree to the right of the current tree. If there is no tree to the right, stop. Note that you do not have any choice after the initial choice of starting tree: you must perform step 1, then step 2, then back to step 1, then step 2, and so on until you stop. You have two baskets, and each basket can carry any quantity of fruit, but you want each basket to only carry one type of fruit each. What is the total amount of fruit you can collect with this procedure? Note: 1 <= tree.length <= 40000 0 <= tree[i] < tree.length
Insight/Solution: Sliding Window, same problem as 159. Longest Substring with At Most Two Distinct Characters except using integers instead of characters this question prompt is really poorly written, so just read 159's question prompt for effectively the same problem
567. Permutation in String Given two strings s1 and s2, write a function to return true if s2 contains the permutation of s1. In other words, one of the first string's permutations is the substring of the second string. Constraints: The input strings only contain lower case letters. The length of both given strings is in range [1, 10,000].
Insight/Solution: Sliding window + counter + auxiliary state variable, literally the same problem as 438. Find All Anagrams in a String except we return True/False and immediately if one permutation (anagram) was found here, the sliding window will be fixed with the length of s1, our counter will be an array of size 26, all initialized to 0, our auxiliary state will also be set to the length of s1, and our opening pointer set to 0 we set up our counter array such that each character's frequency is counted, i.e. [a: 1, b: 2, c: 0, d: 2, etc...], now we can iterate throughout the string s and maintain the fixed window length every iteration, we decrement the count of the current character in our counter array, notice that the characters in s1 will all be greater than 0, so we know that if its counter value is >= 0 after its decrement, we know that we should update our auxiliary state variable since that means we have a one less character to consider for our window if our auxiliary state variable reaches 0, we know that we've found a window that is a permutation of s1 afterwards, we have to check if our window's length == len(s1), and if it is, we have to update our array counter by 1 for the current letter we are on, also update our auxiliary state variable accordingly (ensuring that we only update the letters in s1), and move our opening pointer by 1
438. Find All Anagrams in a String Given a string s and a non-empty string p, find all the start indices of p's anagrams in s. Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100. The order of output does not matter.
Insight/Solution: Sliding window + counter + auxiliary state variable, this problem uses a fixed sliding window since words are anagrams if they follow two principles: the length are the same, and the frequencies of each letter in the words are the same here, the sliding window will be fixed with the length of p, our counter will be an array of size 26, all initialized to 0, our auxiliary state will also be set to the length of p, and our opening pointer set to 0 we set up our counter array such that each character's frequency is counted, i.e. [a: 1, b: 2, c: 0, d: 2, etc...], now we can iterate throughout the string s and maintain the fixed window length every iteration, we decrement the count of the current character in our counter array, notice that the characters in p will all be greater than 0, so we know that if its counter value is >= 0 after its decrement, we know that we should update our auxiliary state variable since that means we have a one less character to consider for our window if our auxiliary state variable reaches 0, we know that we've found a window that is an anagram of p afterwards, we have to check if our window's length == len(p), and if it is, we have to update our array counter by 1 for the current letter we are on, also update our auxiliary state variable accordingly (ensuring that we only update the letters in p), and move our opening pointer by 1
76. Minimum Window Substring Given two strings s and t, return the minimum window in s which will contain all the characters in t. If there is no such window in s that covers all characters in t, return the empty string "". Note that If there is such a window, it is guaranteed that there will always be only one unique minimum window in s. Constraints: 1 <= s.length, t.length <= 105 s and t consist of English letters. Follow up: Could you find an algorithm that runs in O(n) time?
Insight/Solution: Sliding window + hash table + auxiliary state variable, but let's first go over the brute force solution Brute force: find all the substrings and simply check if the characters in t are in the substring, should take O(S^3) Sliding Window: optimized to O(S + T), this problem is very similar to permutation in string and find all anagrams in a string, the idea is to keep the auxiliary state variable and decrement it whenever we land on a character that has yet to be decremented in our hash table, then we also keep track of a min_length and min_window to keep track of the minimum substring a difference we see for this problem compared to permutation in string and find all anagrams in a string is that our window size can vary, meaning we have to accommodate our logic to handle a window with characters that do not belong in the t string, which is okay (instead of an if statement, use a while loop)
234. Palindrome Linked List Given a singly linked list, determine if it is a palindrome. Follow up: Could you do it in O(n) time and O(1) space?
Insight/Solution: Store node values in an array and check if the reverse is the same as the original Follow up: Two pointers + reverse linked list, we have a slow and fast pointer, iterate until fast or fast.next is null, then reverse the linked list at slow, then reset fast to head, then iterate and check if fast and slow are the same
141. Linked List Cycle Given head, the head of a linked list, determine if the linked list has a cycle in it. There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to. Note that pos is not passed as a parameter. Return true if there is a cycle in the linked list. Otherwise, return false.
Insight/Solution: Slow and fast pointers, the fast pointer starts one node after the slow node and moves one extra node further each time if slow == fast: there is a cycle if fast is null or fast.next is null, there is no cycle
217. Contains Duplicate Given an array of integers, find if the array contains any duplicates. Your function should return true if any value appears at least twice in the array, and it should return false if every element is distinct.
Insight/Solution: Sort or use a hash table/hash set, for sort check if adjacent elements are equal, for set check if it is already in the set sorting is O(nlgn) and hash table/hash set is O(n)
268. Missing Number Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array. Follow up: Could you implement a solution using only O(1) extra space complexity and O(n) runtime complexity?
Insight/Solution: Sort, Hash Set, bit manipulation, Gauss' Formula Follow-up: bit manipulation and Gauss' Formula handle this follow-up, for bit manipulation, recognize that there is guaranteed a missing number, but we can collect all the numbers of the range from our index and length of the nums (if the missing number is simply n), then any number that is not missing can be shown twice, while the missing number is shown once, using the XOR operator, a ^ a = 0, b ^ 0 = b, and it has the commutative property as well, so simply XORing will yield the correct solution
169. Majority Element Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times. You may assume that the array is non-empty and the majority element always exist in the array.
Insight/Solution: There are multiple solutions, but we'll go over 3 solutions Sorting: sort and return the middle element, runtime: O(nlgn), space: O(1) or O(n) Hash Table: map each element and have a counter, the element with the highest count is returned, runtime: O(n), space: O(n) Boyer-Moore Voting Algorithm: Assume the first element is the majority element (call it candidate) and set count = 0, every time we see a match in our list, we increment count by 1, -1 if not a match, when we reach count = 0, reset our candidate, our candidate at the end will be the majority element, runtime: O(n), space: O(1)
269. Alien Dictionary There is a new alien language that uses the English alphabet. However, the order among letters are unknown to you. You are given a list of strings words from the dictionary, where words are sorted lexicographically by the rules of this new language. Derive the order of letters in this language, and return it. If the given input is invalid, return "". If there are multiple valid solutions, return any of them. Constraints: 1 <= words.length <= 100 1 <= words[i].length <= 100 words[i] consists of only lowercase English letters.
Insight/Solution: Topological Sort, there are two steps to solving this hard LC problem 1. build a DAG 2. topological sort simple idea, difficult execution, the difficulty comes from building the DAG and handling some interesting edge cases, understand what it means for a word to be lexicographically correct, here is an example: abc, zebra, zoo z > o but lexicographically cares about the order of words, not individual letters, we should only care about letters at its individual level for an example like this: wrt, wrf --> this means t comes before f an edge case to consider is abc, ab which the expected outcome is an empty string if abc is before ab, then c must have some negative order value which doesn't make sense, so we declare this test case invalid using Kahn's algorithm with ascii value indexing will be efficient, easy, and clean
210. Course Schedule II There are a total of n courses you have to take labelled from 0 to n - 1. Some courses may have prerequisites, for example, if prerequisites[i] = [ai, bi] this means you must take the course bi before the course ai. Given the total number of courses numCourses and a list of the prerequisite pairs, return the ordering of courses you should take to finish all courses. If there are many valid answers, return any of them. If it is impossible to finish all courses, return an empty array. Constraints: 1 <= numCourses <= 2000 0 <= prerequisites.length <= numCourses * (numCourses - 1) prerequisites[i].length == 2 0 <= ai, bi < numCourses ai != bi All the pairs [ai, bi] are distinct.
Insight/Solution: Topological Sort, this problem is a topological sort problem in disguise, you can also use Kahn's algorithm which is topological sort using in-degrees
199. Binary Tree Right Side View Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom.
Insight/Solution: Tree + BFS, conduct a level order traversal and the last node in each level is the node we see from the right side
105. Construct Binary Tree from Preorder and Inorder Traversal Given preorder and inorder traversal of a tree, construct the binary tree. Note:You may assume that duplicates do not exist in the tree.
Insight/Solution: Tree + DFS + Queue + Hash Table, the theory is that preorder goes root -> left -> right, and inorder goes left -> root -> right what this means in the arrays is the first node in the preorder array is our root, and the subarray to the left of that value in the inorder array is a left subtree of the root, same goes for the right subarray consider the preorder: [20, 15, 7] and inorder: [15, 20, 7], this means that 20 is the root, 15 is the left subtree, and 7 is the right subtree how to handle the preorder condition? this is where the queue and DFS comes into play, we have to use queue's popleft property to pop off the first element of the preorder array, and we have to use DFS so that we maintain our mutable array throughout our recursive path, if we have a larger tree to consider such as preorder: [3,9,20,15,7] and inorder: [9,3,15,20,7], you'll notice that we want to consider the right subtree once we pop off everything to the left subtree as well, this is why we must do DFS so that we take advantage of the mutable property that lists have in python use a hash table to keep track of where each element of inorder is, we use the index that we get as a way to split up our array into subproblems and eventually reach a base case to terminate our recursion Note: implement the algorithm with just DFS first, then make the small adjustments to optimize
98. Validate Binary Search Tree Given the root of a binary tree, determine if it is a valid binary search tree (BST). A valid BST is defined as follows: The left subtree of a node contains only nodes with keys less than the node's key. The right subtree of a node contains only nodes with keys greater than the node's key. Both the left and right subtrees must also be binary search trees.
Insight/Solution: Tree + DFS, one key principle about BSTs is that its inorder traversal yields a sorted output, so if we conduct an inorder traversal, we can simply check if the previous value was smaller than the current value remember that the inorder traversal follows the rule: left->root->right, you can solve this iteratively or recursively, but if done iteratively, use the stack data structure
283. Move Zeroes Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements. Note: You must do this in-place without making a copy of the array. Minimize the total number of operations.
Insight/Solution: Two pointers + invariant Invariant: all the elements left of the slow pointer are non-zero numbers, everything in between the slow and fast pointers are zeroes
79. Word Search Given an m x n board and a word, find if the word exists in the grid. The word can be constructed from letters of sequentially adjacent cells, where "adjacent" cells are horizontally or vertically neighboring. The same letter cell may not be used more than once. Constraints: m == board.length n = board[i].length 1 <= m, n <= 200 1 <= word.length <= 103 board and word consists only of lowercase and uppercase English letters.
Insight/Solution: Use DFS/backtracking, we have the backtracking function return a boolean, indicating whether we have a match or not, a match is happened if we run out of letters to search for, any time we go out of the board or letters don't match we return false we have to use backtracking so we don't accidentally go back to our previous letter, use a character like '*', and revert back to the original letter after backtracking, this means we should store our current letter in a temp variable afterwards, return the matched boolean variable, if it trickles back to the top as true, we found a word match
179. Largest Number Given a list of non-negative integers nums, arrange them such that they form the largest number. Note: The result may be very large, so you need to return a string instead of an integer.
Insight/Solution: Use a custom comparator and sort, here is a proof by contradiction: suppose a ~ b > b ~ a, and we want to produce an incorrect order, so assume there is a c such that b precedes c but c precedes a, this is impossible because if b ~ c > c ~ a, then a ~ c must be > c ~ a, and so transitivity is maintained in Python3, the default comparator is the __lt__ comparator, so we have to modify that one def__lt__(s1, s2): return s1 + s2 > s2 + s1
21. Merge Two Sorted Lists Merge two sorted linked lists and return it as a new sorted list. The new list should be made by splicing together the nodes of the first two lists.
Insight/Solution: Use a sentinel node and update accordingly while l1 and l2 Note: remember to add the last node after the while loop iteration and return the next node of our sentinel node
242. Valid Anagram Given two strings s and t , write a function to determine if t is an anagram of s. Note:You may assume the string contains only lowercase alphabets. Follow up: What if the inputs contain unicode characters? How would you adapt your solution to such case?
Insight/Solution: Use an array of size 26 to keep track of counts, the index is based off the character, so we start at 'a' = 0 -> 'z' = 25, for every iteration of a letter for s and t, we increment by 1 and decrement by 1, respectively iterate through the array again and if we find a value not equal to 0, we know it means s and t were not anagrams Follow-up: use a hash table instead of a fixed-size array
261. Graph Valid Tree Given n nodes labeled from 0 to n-1 and a list of undirected edges (each edge is a pair of nodes), write a function to check whether these edges make up a valid tree. Note: you can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0,1] is the same as [1,0] and thus will not appear together in edges.
Insight/Solution: Very similar problem to Course Schedule with a few more constraints, in graph theory, a graph is also a tree with the following: - the graph must be acyclic - the graph must be connected in Course Schedule, we already handled the cycle detection, in addition, we have to check if we visit all nodes or not (therefore, we should only call DFS once), and since we consider the graph undirected, we also have to pass an extra parameter (parent) to check if our vertex is a parent (if so, we should not consider it, or else our algorithm will consider it a cycle)
1. Two Sum Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
Insight/Solution: a + b = x, so use a hash table to check if b = x - a is in it
70. Climbing Stairs You are climbing a staircase. It takes n steps to reach the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Insight/Solution: classic DP problem, essentially fibonacci sequence since we can take a step of size 1 or 2, we can produce this recursive expression dp[i] = 1 if i = 0 or i = 1 dp[i] = dp[i - 1] + dp[i - 2] if i > 1
215. Kth Largest Element in an Array Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element. Note: You may assume k is always valid, 1 ≤ k ≤ array's length.
Insight/Solution: heap, can actually be solved using either a max heap or min heap, but using a min heap will be optimal with a max heap: initialize a max heap with the nums and pop the heap k times, and the kth element is the answer with a min heap: initialize an empty heap and construct a min heap with the values, but as we keep adding them, we also pop out values if our min heap's size exceeds k, this effectively will maintain the largest elements of the heap up to size k, and we can just pop once more after iterating to get the kth largest element Note: python's heapq module includes a function that does this already: heapq.nlargest(k, nums) which outputs an array sorted in descending order, there is also a function for smallest (heap.nsmallest) analysis: max-heap's time and space complexities are as follows: O(nlgn) and O(n), for the min-heap, they are as follows: O(nlgk) and O(k) There is in fact an even better solution in O(n) and O(1), it is a divide and conquer technique called Hoare's selection algorithm (Quickselect), something Professor Miguel also taught to us, need to look in the future and implement using this algorithm
238. Product of Array Except Self Given an array nums of n integers where n > 1, return an array output such that output[i] is equal to the product of all the elements of nums except nums[i]. Constraint: It's guaranteed that the product of the elements of any prefix or suffix of the array (including the whole array) fits in a 32 bit integer. Note: Please solve it without division and in O(n). Follow up: Could you solve it with constant space complexity? (The output array does not count as extra space for the purpose of space complexity analysis.)
Insight/Solution: left and right product lists, we basically construct an array for the product of numbers to an element's left and it's right, afterwards, multiply each element at left with the corresponding right and store into an output array Follow-up: same approach as before, except we don't store the products in an array, instead, we multiply an integer variable as we move forward, so suppose we start with our left products, then as we move along our output array, we set it to left, then multiply left by the current location we are at (use an example, it will make more sense), do the same approach with right products except we go in reverse and multiply output[i] with right, not set output[i] = right Note: the two extremes at the end only require a product from one side, the far left element does not have any left products, the far right element does not have any right products, use 1 as a fill-in value due to the identity property of multiplication
152. Maximum Product Subarray Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product.
Insight/Solution: one of those weird DP/greedy problems, we keep track of local solutions and build our global solution off the local solutions Similar idea to the maximum subarray problem, except we handle products which have a twist, the twist has to do with handling two cases: zeroes and negative numbers recall that the greedy choice in the maximum subarray problem was local_max = max(num, num + local_max) global_max = max(local_max, global_max) the idea is a great start, so consider for this problem the greedy choice foundation local_max = max(num, num * local_max) global_max = max(local_max, global_max) handling negative numbers means there is a potential for a local_max to suddenly switch to being the smallest product, and the opposite is true too, the smallest product can suddenly switch to being the largest product, so it would be wise to keep track of the smallest products as well, and consider their product with num as another condition for checking the max so now we have local_max = max(num, num * local_max, num * local_min) local_min = min(num, num * local_max, num * local_min) global_max = max(local_max, global_max) zeroes are handled accordingly with the condition of checking num and multiplying by num, that will effectively reset our multiplication chain and start fresh for the next number, our global_max will still hold onto the max product we solved before the zero, so if there is an even larger max product after the zero, our third line for computing the global_max will handle that case accordingly
20. Valid Parentheses Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid. An input string is valid if: Open brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order.
Insight/Solution: the idea is that the most recent open parenthesis will match the first closing parenthesis, hence we can use a LIFO data structure Use a stack to add all the open parentheses and pop whenever we hit a closed parenthesis.
207. Course Schedule There are a total of numCourses courses you have to take, labeled from 0 to numCourses-1. Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1] Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses? Constraints: The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented. You may assume that there are no duplicate edges in the input prerequisites. 1 <= numCourses <= 10^5
Insight/Solution: this problem is disguised as a graph cycle detection problem, recognize that we could build our pairs as a directed graph Notice that we can have disconnected graphs, and that we only fail to complete all courses if there is a cycle, use DFS with the labeling of -1, 0, and 1 instead 0 = unvisited -1 = visiting 1 = visited this is important to note because if we had done a standard T/F method, our algorithm will allow some edges to be considered as a cycle (which may or may not be true)
226. Invert Binary Tree Invert a binary tree.
Insight/Solution: very easily done via recursion (idea that trees have subtrees, if they have children) if the head is null, return it head->left = inverse of head->right head->right = inverse of head->left can do this in one line with python
287. Find the Duplicate Number Given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive. There is only one duplicate number in nums, return this duplicate number. Follow-ups: How can we prove that at least one duplicate number must exist in nums? Can you solve the problem without modifying the array nums? Can you solve the problem using only constant, O(1) extra space? Can you solve the problem with runtime complexity less than O(n2)? Constraints: 2 <= n <= 3 * 104 nums.length == n + 1 1 <= nums[i] <= n All the integers in nums appear only once except for precisely one integer which appears two or more times.
Insight/Solution: we'll break up the solutions by their follow-ups: you can sort the list and check for adjacent duplicates 1.) pigeonhole principle, if n objects are put into m containers, and n > m, then there will be at least one container with multiple objects 2). since sorting will modify the list, we can use a hash set instead to store the numbers we have seen in the array 3). since using a hash set means we use space, we have to opt for a different algorithm, this one allows for the brute force, so simply run a nested for-loop 4). rip, they said now make it faster, we can use Floyd's Tortoise and Hare algorithm (phase 1: tortoise = nums[tortoise], hare = nums[nums[hare]], phase 2: both move at the same pace), essentially, we treat the value of our array as pointers to nodes