Binary search the result.
If the sum > threshold, the divisor is too small.
If the sum <= threshold, the divisor is big enough.
Run time: $O(n\log m)$, space: $O(1)$, where $m=\max(A)$.
1 2 3 4 5 6 7 8 9
defsmallestDivisor(self, A, threshold): l, r = 1, max(A) while l < r: m = (l + r) / 2 ifsum((i + m - 1) / m for i in A) > threshold: l = m + 1 else: r = m return l
defbook(self, start: int, end: int) -> bool: index = self.bise(start) if index == len(self.arr): self.arr.append((start, end)) returnTrue if end > self.arr[index][0]: returnFalse self.arr = self.arr[:index] + [(start, end)] + self.arr[index:] returnTrue #bisect_right self-implementation search by key= lambda x: x[1] defbise(self, val): ifnot self.arr: return0 if val >= self.arr[-1][1]: returnlen(self.arr) l, r = 0, len(self.arr)-1 while l < r: m = (l + r) // 2 if self.arr[m][1] <= val: l = m+1 else: r = m return l
# Your MyCalendar object will be instantiated and called as such: # obj = MyCalendar() # param_1 = obj.book(start,end)
302. Smallest Rectangle Enclosing Black Pixels
top = search row [0…x], find first row contain 1,
bottom = search row[x+1, row], find first row contian all 0
left = search col[0…y], find first col contain 1,
right = search col[y+1, col], find first col contain all 0
deffindClosestElements(self, arr: List[int], k: int, x: int) -> List[int]: lo, hi = 0, len(arr) - k while lo < hi: mid = lo + (hi-lo)//2 if x - arr[mid] > arr[mid + k] - x: lo = mid + 1 else: hi = mid return arr[lo:lo + k]
354. Russian Doll Envelopes
*calculate the longest increasing subsequence (LIS). We first sort the envelopes by width, and we also make sure that when the width is the same, the envelope with greater height comes first. We could calculate the LISS using the standard DP algorithm (quadratic runtime), but we can just use the tails array method with a twist: we store the index of the tail, and we do leftmost insertion point as usual to find the right index in nlogn time. Why not rightmost? Think about the case [1, 1], [1, 1], [1, 1]. https://leetcode.com/problems/russian-doll-envelopes/discuss/82761/Python-O(nlogn)-O(n)-solution-beats-97-with-explanation
Run time: $O(n\log n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defmaxEnvelopes(self, envelopes: List[List[int]]) -> int: ifnot envelopes: return0 n = len(envelopes) envelopes.sort(key=lambda x: (x[0], -x[1])) dp = [0] * n ans = 0 for _, h in envelopes: lo, hi = 0, ans-1 while lo <= hi: mid = lo + (hi-lo)//2 if dp[mid] >= h: hi = mid-1 else: lo = mid+1 dp[lo] = h ans = max(ans, lo+1) return ans
1011. Capacity To Ship Packages Within D Days
Set lo to be the max weight, set height to be the total weight. Use binary search to find the minimum capacity so that all packages could be shipped in time, and we need to return lo in the end as the lower bound.
defshipWithinDays(self, weights: List[int], days: int) -> int: defdoable(cap): num_day = 1 curr_w = 0 for w in weights: curr_w += w if curr_w > cap: curr_w = w num_day += 1 if num_day > days: returnFalse returnTrue lo, hi = max(weights), sum(weights) while lo < hi: mid = lo + (hi-lo) // 2 if doable(mid): hi = mid else: lo = mid+1 return lo
defsplitArray(self, nums: List[int], m: int) -> int: deffeasible(threshold) -> bool: count = 1 total = 0 for num in nums: total += num if total > threshold: total = num count += 1 if count > m: returnFalse returnTrue
left, right = max(nums), sum(nums) while left < right: mid = left + (right - left) // 2# target sum if feasible(mid): right = mid else: left = mid + 1 return left
(TLE) Let dp(i, m) denote the minimum the largest sum among subarrays when dividing nums[i…n-1] into m subarrays.
Run time: $O(mn^2)$, space: $O(mn)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defsplitArray(self, nums: List[int], m: int) -> int: n = len(nums) cum_sum = [0]*n dp = [[float('inf')]*n for _ inrange(m+1)] cum_sum[0] = nums[0] for i inrange(1, n): cum_sum[i] = cum_sum[i-1]+nums[i] for i inrange(n): dp[1][i] = cum_sum[i]
for i inrange(2, m+1): for j inrange(i-1, n): for k inrange(j): dp[i][j] = min(dp[i][j], max(dp[i-1][k], cum_sum[j] - cum_sum[k])) return dp[m][n-1]
deffindKthPositive(self, arr: List[int], k: int) -> int: l, r = 0, len(arr) while l < r: m = l + (r - l)//2 if arr[m] - (m+1) < k: # get the correct index l = m + 1 else: r = m return l + k
defnumMatchingSubseq(self, s: str, words: List[str]) -> int: ans = 0 mappings = defaultdict(list) for index, char inenumerate(s): mappings[char].append(index)
for word in words: prev = -1 found = True for c in word: tmp = bisect.bisect(mappings[c], prev) if tmp == len(mappings[c]): # not found, returned last index found = False break else: prev = mappings[c][tmp] if found: ans += 1 return ans
162. Find Peak Element
Check if nums[m] < nums[m+1]. If so, it means the possible answer might not be on the left side, so set lo=mid+1.
Run time: $O(\log n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9
deffindPeakElement(self, nums: List[int]) -> int: lo, hi = 0, len(nums)-1 while lo < hi: m = lo + (hi-lo)//2 if nums[m] < nums[m+1]: # here m is low mid so here is nerver a case m+1 = h while l<h lo = m+1# possible answer right side without the mid if nums[m] > nums[m+1]: hi = m # possible answer left side the mid return lo
1 2 3 4 5 6 7 8 9 10
deffindPeakElement(self, nums: List[int]) -> int: defhelper(nums, lo, hi): if lo == hi: return lo mid = lo + (hi-lo) //2 if nums[mid] > nums[mid+1]: return helper(nums, lo, mid) return helper(nums, mid+1, hi) return helper(nums, 0, len(nums)-1)
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5
deffindPeakElement(self, nums: List[int]) -> int: for i inrange(len(nums)-1): if nums[i] > nums[i+1]: return i return nums[len(nums)-1]
240. Search a 2D Matrix II
> Search from bottom left corner. Move up if target is smaller, move right if target is greater.
defsearchMatrix(self, matrix: List[List[int]], target: int) -> bool: ifnotlen(matrix) ornotlen(matrix[0]): returnFalse h, w = len(matrix), len(matrix[0]) i, j = h-1, 0# bottom left corner whileTrue: if i < 0or j >= w: break current = matrix[i][j] if target < current: # target is smaller, then go up i -= 1 elif target > current: # target is larger, then go right j += 1 else:# hit target returnTrue returnFalse
Recursion.
Run time: $O(mn)$, space: $O(mn)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defsearchMatrix(self, matrix: List[List[int]], target: int) -> bool: defhelper(matrix, target, i, j, s): if i >= m or j >= n or i < 0or j < 0or (i,j) in s: returnFalse s.add((i,j)) if matrix[i][j] == target: returnTrue elif matrix[i][j] < target: return helper(matrix, target, i+1, j, s) or \ helper(matrix, target, i, j+1, s) else: return helper(matrix, target, i-1, j, s) or \ helper(matrix, target, i, j-1, s) if target > matrix[-1][-1]: returnFalse m,n = len(matrix), len(matrix[0]) return helper(matrix, target, 0,0, set())
81. Search in Rotated Sorted Array II
> Three cases: if `nums[mid] == target`, return True. If `nums[mid]` is equal to `nums[lo]`, we could not determine which part of the array is rotated, therefore we increment `lo` by 1. If `nums[mid]`is smaller than or equal to `nums[hi]`, then we know the right part may be monotone increasing. We check if the target falls into this interval and update the index. The last case is whether the target falls into the intervals of `lo` and `mid`.
defsearch(self, nums, target): """ :type nums: List[int] :type target: int :rtype: bool """ ifnot nums: returnFalse lo = 0 hi = len(nums)-1 while lo <= hi: mid = lo + (hi-lo)//2 if nums[mid] == target: returnTrue if nums[lo] == nums[mid]: lo += 1 elif nums[mid] <= nums[hi]: if nums[mid] < target <= nums[hi]: lo = mid+1 else: hi = mid-1 else: if nums[lo] <= target < nums[mid]: hi = mid-1 else: lo = mid+1 returnFalse
34. Find First and Last Position of Element in Sorted Array
> If the while condition has equal sign, then every case needs to increment or decrement by 1.
defsearchRange(self, nums, target): """ :type nums: List[int] :type target: int :rtype: List[int] """ deflower_bound(): lo = 0 hi = len(nums) while lo <= hi: mid = lo + (hi-lo)//2 if nums[mid] >= target: hi = mid-1 else: lo = mid+1 return lo defupper_bound(): lo = 0 hi = len(nums) while lo <= hi: mid = lo + (hi-lo)//2 if nums[mid] <= target: lo = mid+1 else: hi = mid-1 return hi ifnot nums: return [-1,-1] first = lower_bound() last = upper_bound() if first >= last: return [-1,-1] return [first, last]
Sorting
>
Run time: $O()$, space: $O()$
1
853. Car Fleet
Pair [position, speed] and sort by position in descending order, then calculate the time it takes to get to the target. Loop through each time, if the current car takes less time than previous car to get to target, then it implies it could catch up with the car in front of it, so they can form a fleet. If it takes more time than the previous car, then it needs to form a new fleet itself. https://leetcode.com/problems/car-fleet/discuss/139850/C%2B%2BJavaPython-Straight-Forward
Run time: $O(n\log n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10
defcarFleet(self, target: int, position: List[int], speed: List[int]) -> int: sorted_position = sorted(zip(position, speed), reverse = True) time_to_dest = [float((target-pos)/sp) for pos, sp in sorted_position] ans, cur = 0,0 for t in time_to_dest: if t > cur: ans += 1 cur = t return ans
378. Kth Smallest Element in a Sorted Matrix
Apply the Binary Search on the number range instead of the index range. As we know that the smallest number of our matrix is at the top left corner and the biggest number is at the bottom lower corner. These two number can represent the range i.e., the start and the end for the Binary Search. This does sound a bit counter-intuitive now, however, it will start to make sense soon. We are all accustomed to the linear array binary search algorithm.
defkthSmallest(self, matrix: List[List[int]], k: int) -> int: m, n = len(matrix), len(matrix[0]) # For general, the matrix need not be a square
defcountLessOrEqual(x): cnt = 0 c = n - 1# start with the rightmost column for r inrange(m): while c >= 0and matrix[r][c] > x: c -= 1# decrease column until matrix[r][c] <= x cnt += (c + 1) return cnt
left, right = matrix[0][0], matrix[-1][-1] ans = -1 while left <= right: mid = (left + right) // 2 if countLessOrEqual(mid) >= k: ans = mid right = mid - 1# try to looking for a smaller value in the left side else: left = mid + 1# try to looking for a bigger value in the right side
return ans
Maintain a min heap with size k.
Run time: $O(mn\log k)$, space: $O(k)$
1 2 3 4 5 6 7 8 9
defkthSmallest(self, matrix: List[List[int]], k: int) -> int: m, n = len(matrix), len(matrix[0]) # For general, matrix doesn't need to be a square maxHeap = [] for r inrange(m): for c inrange(n): heapq.heappush(maxHeap, -matrix[r][c]) iflen(maxHeap) > k: heapq.heappop(maxHeap) return -heapq.heappop(maxHeap)
Flatten the matrix and sort the array.
Run time: $O(mn\log (mn))$, space: $O(mn)$
1 2 3 4 5
defkthSmallest(self, matrix: List[List[int]], k: int) -> int: res = [] for r in matrix: res += r returnsorted(res)[k-1]
Topological Sort
2115. Find All Possible Recipes from Given Supplies
Put the <ingredient, list of what recipes need> in a dictionary, and another dict <recipe, number of ingredients>. Put all supplies to deque, pull from it and update the number of ingredients of the recipe it could make. If the recipe could be made, put it to queue. In the end, for each recipe if their number of ingredients needed is less than 0, put it in output array.
deffindAllRecipes(self, recipes: List[str], ingredients: List[List[str]], supplies: List[str]) -> List[str]: numIngredients = Counter() parentOf = defaultdict(list) for rec, ing inzip(recipes, ingredients): for i in ing: parentOf[i].append(rec) numIngredients[rec] += 1 q = deque([i for i in supplies]) ans = [] while q: s = q.popleft() for canMake in parentOf[s]: numIngredients[canMake] -= 1 if numIngredients[canMake] == 0: q.append(canMake) for recipe in recipes: if numIngredients[recipe] <= 0: ans.append(recipe) return ans
defsum(self, row: int, column: str, numbers: List[str]) -> int: u = (row,column) self.unset(u) self.g[u] = self.itemize(numbers) for v in self.g[u]: self.inv[v].append(u) self.grid[u] = sum(self.grid[v] for v in self.g[u])
self.propagate(u) return self.grid[u] # unsets sum dependencies of u defunset(self, u): if u in self.g: for v in self.g[u]: self.inv[v].remove(u) del self.g[u] # propagates updates to all direct and indirect dependents of root defpropagate(self, root): indegree = Counter() @cache defrecurse(u): for v in self.inv[u]: indegree[v] += 1 recurse(v) recurse(root) q = [root] for u in q: if u != root: self.grid[u] = sum(self.grid[v] for v in self.g[u]) for v in self.inv[u]: indegree[v] -= 1 if indegree[v] == 0: q.append(v) # returns (r,c) cells defitemize(self, strs): ret = [] for s in strs: if":"in s: start,end = s.split(":") sr,sc = int(start[1:]), start[0] er,ec = int(end[1:]), end[0] for r inrange(sr, er+1): for c inmap(chr, range(ord(sc), ord(ec)+1)): ret.append((r,c)) else: r,c = int(s[1:]), s[0] ret.append((r,c)) return ret # Your Excel object will be instantiated and called as such: # obj = Excel(height, width) # obj.set(row,column,val) # param_2 = obj.get(row,column) # param_3 = obj.sum(row,column,numbers)
Use dictionary to store each letter’s value, and occurrences.
def__init__(self, height: int, width: str): self.cells = [{letter: {"value": 0, "sum": None} for letter in string.ascii_uppercase} for h inrange(height + 1)]
defsum(self, row: int, column: str, numbers: List[str]) -> int: self.cells[row][column]["sum"] = self._parse(numbers) return self.get(row, column) def_parse(self, strs: List[str]): counter = Counter() for s in strs: if":"in s: start, end = s.split(":")[0], s.split(":")[1] else: start = end = s for r inrange(int(start[1:]), int(end[1:]) + 1): for ci inrange(ord(start[0]), ord(end[0]) + 1): counter[(r, chr(ci))] += 1 return counter
# Your Excel object will be instantiated and called as such: # obj = Excel(height, width) # obj.set(row,column,val) # param_2 = obj.get(row,column) # param_3 = obj.sum(row,column,numbers)
deflongestIncreasingPath(self, matrix: List[List[int]]) -> int: M, N = len(matrix), len(matrix[0]) outdegree = { (i, j): 0for i inrange(M) for j inrange(N)} dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)] adj = defaultdict(list) for i inrange(M): for j inrange(N): for x, y in dirs: dx = i + x dy = j + y if (dx, dy) in outdegree and \ matrix[i][j] < matrix[dx][dy]: outdegree[i,j] += 1 adj[dx, dy].append((i, j))
stack = [] for key in outdegree: if outdegree[key] == 0: stack.append(key)
h = 0 while stack: h += 1 newstack = [] for x, y in stack: for next_x, next_y in adj[x, y]: outdegree[next_x, next_y] -= 1 if outdegree[next_x, next_y] == 0: newstack.append((next_x, next_y)) stack = newstack return h
We can find longest decreasing path instead, the result will be the same. Use dp to record previous results and choose the max dp value of smaller neighbors.
# initilization M, N = len(matrix), len(matrix[0]) # length, width dp = [[0]*N for i inrange(M)] # 2-D matrix for store the number of steps
# dfs function defdfs(i, j): ifnot dp[i][j]: # if this position is not visited val = matrix[i][j] # search four directions to find out the decreasing path # up if i > 0and val > matrix[i-1][j]: up = dfs(i-1, j) else: up = 0 # down if i < M-1and val > matrix[i+1][j]: down = dfs(i+1, j) else: down = 0 # left if j > 0and val > matrix[i][j-1]: left = dfs(i, j-1) else: left = 0 # right if j < N-1and val > matrix[i][j+1]: right = dfs(i, j+1) else: right = 0 # "walk" to the target neighbor and accumulate the number of steps dp[i][j] = 1 + max(up, down, left, right) return dp[i][j]
res_path = [] for x inrange(M): # search the grid by dfs for y inrange(N): res_path.append(dfs(x, y))
defalienOrder(self, words: List[str]) -> str: defbuild_graph(words): graph, degree = {}, {} # initialize for w in words: for ch in w: if ch in graph: continue graph[ch] = [] if ch in degree: continue degree[ch] = 0 # build graph for i inrange(len(words) - 1): w1, w2 = words[i], words[i + 1] min_len = min(len(w1), len(w2)) found = False for i inrange(min_len): if w1[i] != w2[i]: # w1[i] < w2[i] if w2[i] notin graph[w1[i]]: graph[w1[i]].append(w2[i]) degree[w2[i]] += 1 found = True break
# a flag to prevent cases like "abc", "ab" # while "ab", "abc" is okay if found == Falseandlen(w1) > len(w2): return"", "" return graph, degree
graph, degree = build_graph(words) source = deque() for key in degree: if degree[key] == 0: source.append(key) res = [] while source: char = source.popleft() res.append(char) for child in graph[char]: degree[child] -= 1 if degree[child] == 0: source.append(child) iflen(res) < len(degree): return"" return''.join(res)
deffindOrder(self, numCourses, prerequisiites: List[List[int]]) -> List[int]: n = numCourses src, dst = collections.Counter(), [set() for _ inrange(n)] for d, s in prerequisites: src[d] += 1 dst[s].add(d) ans = [x for x inrange(n) ifnot src[x]] for s in ans: for d in dst[s]: src[d] -= 1 ifnot src[d]: ans.append(d) return ans iflen(ans) == n else []
deffindOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: # Create a prerequisite dict. (containing courses (nodes) that need to be taken (visited) # before we can visit the key. preq = {i:set() for i inrange(numCourses)} # Create a graph for adjacency and traversing. graph = collections.defaultdict(set) for i,j in prerequisites: # Preqs store requirments as their given. preq[i].add(j) # Graph stores nodes and neighbors. graph[j].add(i) q = collections.deque([]) # We need to find a starting location, aka courses that have no prereqs. for k, v in preq.items(): iflen(v) == 0: q.append(k) # Keep track of which courses have been taken. taken = [] while q: course = q.popleft() taken.append(course) # If we have visited the numCourses we're done. iflen(taken) == numCourses: return taken # For neighboring courses. for cor in graph[course]: # If the course we've just taken was a prereq for the next course, remove it from its prereqs. preq[cor].remove(course) # If we've taken all of the preqs for the new course, we'll visit it. ifnot preq[cor]: q.append(cor) # If we didn't hit numCourses in our search we know we can't take all of the courses. return []
deffindOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: n = numCourses G = [set() for _ inrange(n)] for d, s in prerequisites: G[s].add(d) vis, orders = [0] * n, []
defdfs_circle(x): vis[x] = -1 for y in G[x]: if vis[y] < 0or (not vis[y] and dfs_circle(y)): returnTrue vis[x] = 1 orders.append(x) returnFalse
for x inrange(numCourses): ifnot vis[x] and dfs_circle(x): return [] return orders[::-1]
Other
>
Run time: $O()$, space: $O()$
1
850. Rectangle Area II
Actually, we can do our interval merge in O(n) time instead of O(n^2). First, let us create all unique y coordinates: set ys, then we create correspondences between indexes and these y coordinates: this is y_i dictionary. Finally, count will be count: how many times each segment is covered. We again create sides like in previous approach.
Next, we again iterate through our sides and update area, but now when we meet new segment, we update count} for range: +1 for start of rectangle and -1 for end of rectangle. Finally, we update our cur_y_sum: we check if count is 0 and if it is more than 0, we add length of this segment to cur_y_sum.
Run time: $O(n^2)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defrectangleArea(self, rectangles): ys = sorted(set([y for x1, y1, x2, y2 in rectangles for y in [y1, y2]])) y_i = {v: i for i, v inenumerate(ys)} count = [0] * len(y_i) sides_lft = [(x1,-1,y1,y2) for x1,y1,x2,y2 in rectangles] sides_rgh = [(x2,1,y1,y2) for x1,y1,x2,y2 in rectangles] sides = sorted(sides_lft + sides_rgh) cur_x = cur_y_sum = area = 0 for x, op_cl, y1, y2 in sides: area += (x - cur_x) * cur_y_sum cur_x = x for i inrange(y_i[y1], y_i[y2]): count[i] += op_cl cur_y_sum = sum(y2 - y1 if c else0for y1, y2, c inzip(ys, ys[1:], count)) return area % (10 ** 9 + 7)
defrectangleArea(self, rectangles: List[List[int]]) -> int: defmerge(intervals): ans = [] for beg, end insorted(intervals): ifnot ans or ans[-1][1] < beg: ans += [[beg, end]] else: ans[-1][1] = max(ans[-1][1], end) returnsum(j-i for i,j in ans) sides_lft = [(x1,0,y1,y2) for x1,y1,x2,y2 in rectangles] sides_rgh = [(x2,1,y1,y2) for x1,y1,x2,y2 in rectangles] sides = sorted(sides_lft + sides_rgh) intervals, ans, prev_x = [], 0, sides[0][0]
for x, op_cl, y1, y2 in sides: ans += merge(intervals) * (x - prev_x) if op_cl == 0: intervals.append((y1,y2)) else: intervals.remove((y1,y2)) prev_x = x return ans % (10**9 + 7)
253. Meeting Rooms II
Use a min heap to put the end time of each meeting. Sort the array by the start time, and for each interval, if start time is smaller than or equal to the end time of the current meeting in the heap that’s ending soonest, pop from the heap.
heapq.heappush(h, intervals[0][1]) for i in intervals[1:]: if h and i[0] >= h[0]: heapq.heappop(h) # a new room is allocated heapq.heappush(h, i[1]) returnlen(h)
56. Merge Intervals
Question: How do you add intervals and merge them for a large stream of intervals? (Facebook Follow-up Question)
Run time: $O(n\log n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10
defmerge(self, intervals: List[List[int]]) -> List[List[int]]: heapify(intervals) merged = [] for i inrange(len(intervals)): root = heappop(intervals) if merged and merged[-1][1]>=root[0]: merged[-1][1] = max(merged[-1][1], root[1]) else: merged.append(root) return merged
1 2 3 4 5 6 7 8 9
defmerge(self, intervals: List[List[int]]) -> List[List[int]]: intervals.sort(key=lambda x: x[0]) merged = [] for interval in intervals: ifnot merged or merged[-1][1] < interval[0]: merged.append(interval) else: merged[-1][1] = max(merged[-1][1], interval[1]) return merged
169. Majority Element
Boyer-Moore Voting Algorithm
If we had some way of counting instances of the majority element as +1+1 and instances of any other element as -1−1, summing them would make it obvious that the majority element is indeed the majority element.
Put into a dictionary, and find the one with max value.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8
defmajorityElement(self, nums: List[int]) -> int: count = Counter(nums).items() max_count, maj = 0, -1 for k, v in count: if v > max_count: max_count = v maj = k return maj
347. Top K Frequent Elements
bucket sort. Create a list of list of length len(nums)+1, where index serves as frequency. Use a dictionary or Counter to get the frequencies of each number, and loop through each key value pair, and put the key to the corresponding buckets. Loop through the buckets reversely, if it is not empty, append the number.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
buckets = [[] for _ inrange(len(nums) + 1)] number_count = collections.defaultdict(int) for num in nums: number_count[num] += 1 for num, freq in number_count.items(): buckets[freq].append(num)
# buckets is a double array flat_list = [] # traverse from right to left so number with higher frequency come first for i inrange(len(buckets) - 1, -1, -1): bucket = buckets[i] if bucket: for num in bucket: flat_list.append(num) return flat_list[:k]
Use Counter to calculate the count for each number, which returns a dict. Then put the keys into a heap, sort by values, key=count.get, use heapq.nlargest.
Run time: $O(n\log k)$, space: $O(n+k)$
1 2 3 4 5 6 7 8 9 10
deftopKFrequent(self, nums, k): """ :type nums: List[int] :type k: int :rtype: List[int] """ if k == len(nums): return nums count = Counter(nums) return heapq.nlargest(k, count.keys(), key=count.get)
215. Kth Largest Element in an Array
Quick Select.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
deffindKthLargest(self, nums, k): ifnot nums: return pivot = random.choice(nums) left = [x for x in nums if x > pivot] mid = [x for x in nums if x == pivot] right = [x for x in nums if x < pivot] L, M = len(left), len(mid) if k <= L: return self.findKthLargest(left, k) elif k > L + M: return self.findKthLargest(right, k - L - M) else: return mid[0]
Use heapq.nlargest(k, nums) and get the last element in the return array.
Run time: $O(n\log k)$, space: $O(k)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
deffindKthLargest(self, nums, k): """ :type nums: List[int] :type k: int :rtype: int """ # heap = [] # for n in nums: # if len(heap) == k: # if heap[0] < n: # heappop(heap) # heappush(heap, n) # else: # heappush(heap, n) # return heap[-k] return heapq.nlargest(k, nums)[-1]
528. Random Pick with Weight
Use binary search to find the target index
Run time: $O(n)$, $O(\log n)$, space: $O(n)$, $O(1)$
classSolution(object): def__init__(self, w): """ :type w: List[int] """ self.prefix_sums = [] total = 0 for weight in w: total += weight self.prefix_sums.append(total) self.total_sum = total defpickIndex(self): """ :rtype: int """ rand = random.random() * self.total_sum lo, hi = 0, len(self.prefix_sums) while lo < hi: mid = lo + (hi-lo)//2 if rand > self.prefix_sums[mid]: lo = mid + 1 else: hi = mid return lo
Keep track of cumulative sum and put it in an array, also get the total sum. Generate a number from 0 to 1, and scale with the total sum. If it is smaller than current cumulative sum, return the index.
Run time: $O(n)$, $O(n)$, space: $O(n)$, $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution(object): def__init__(self, w): """ :type w: List[int] """ d = sum(w) print(d) self.weighted = [float(a)/d for a in w] defpickIndex(self): """ :rtype: int """ rand = random.random() for i, w inenumerate(self.weighted): if rand < w: return i
defkClosest(self, points, k): return self.quick_select(points, k) defquick_select(self, points, k): """Perform the QuickSelect algorithm on the list""" left, right = 0, len(points) - 1 pivot_index = len(points) while pivot_index != k: # Repeatedly partition the list # while narrowing in on the kth element pivot_index = self.partition(points, left, right) if pivot_index < k: left = pivot_index else: right = pivot_index - 1 # Return the first k elements of the partially sorted list return points[:k] defpartition(self, points, left, right): """Partition the list around the pivot value""" pivot = self.choose_pivot(points, left, right) pivot_dist = self.squared_distance(pivot) while left < right: # Iterate through the range and swap elements to make sure # that all points closer than the pivot are to the left if self.squared_distance(points[left]) >= pivot_dist: points[left], points[right] = points[right], points[left] right -= 1 else: left += 1 # Ensure the left pointer is just past the end of # the left range then return it as the new pivotIndex if self.squared_distance(points[left]) < pivot_dist: left += 1 return left defchoose_pivot(self, points, left, right): """Choose a pivot element of the list""" return points[left + (right - left) // 2] defsquared_distance(self, point): """Calculate and return the squared Euclidean distance.""" return point[0] ** 2 + point[1] ** 2
Binary Search: set the lo and hi to be the min (0) and max euclidean distance in the list of points. Then perform binary search to loop through all points in this range and classify them into two lists: distance smaller than mid, and distance greater than mid. If the length of closer list is equal to or less than k, then all of them would be included in the final result and we will skip them in the next search and update the k.
Run time: $O(n)$, space: $O(n)$
defkClosest(self, points, k): # Precompute the Euclidean distance for each point distances = [self.euclidean_distance(point) for point in points] # Create a reference list of point indices remaining = [i for i inrange(len(points))] # Define the initial binary search range low, high = 0, max(distances) # Perform a binary search of the distances # to find the k closest points closest = [] while k: mid = low + (high - low) / 2 closer, farther = self.split_distances(remaining, distances, mid) iflen(closer) > k: # If more than k points are in the closer distances # then discard the farther points and continue remaining = closer high = mid else: # Add the closer points to the answer array and keep # searching the farther distances for the remaining points k -= len(closer) closest.extend(closer) remaining = farther low = mid # Return the k closest points using the reference indices return [points[i] for i in closest]
defsplit_distances(self, remaining, distances, mid): """Split the distances around the midpoint and return them in separate lists.""" closer, farther = [], [] for index in remaining: if distances[index] <= mid: closer.append(index) else: farther.append(index) return [closer, farther]
defeuclidean_distance(self, point): """Calculate and return the Euclidean distance.""" return sqrt(point[0] ** 2 + point[1] ** 2)
Maintain a max heap with size $k$ that stores the negative value of the squares of coordinates. Once heap size exceeds $k$, pop the element.
Run time: $O(n\log k)$, space: $O(k)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defkClosest(self, points, k): """ :type points: List[List[int]] :type k: int :rtype: List[List[int]] """ heap = [] for point in points: dist = point[0] * point[0] + point[1] * point[1] heapq.heappush(heap, (-dist, point)) iflen(heap) > k: heapq.heappop(heap)
while q: size = len(q) depth += 1 for _ inrange(size): node = q.popleft() if node == target: return depth if node in visited: continue visited.add(node) q.extend(self.successors(node)) return -1
defsuccessors(self, src): res = [] for i, ch inenumerate(src): num = int(ch) res.append(src[:i] + str((num - 1) % 10) + src[i+1:]) res.append(src[:i] + str((num + 1) % 10) + src[i+1:]) return res
1091. Shortest Path in Binary Matrix
Use a queue and a set to keep track of visited position and current length of path. The first time we see the lower right corner in the queue would be the shortest path.
while start_q andnot found: next_q = set() wordList -= set(start_q) for word in start_q: for i inrange(n): first, second = word[:i], word[i+1:] for c in'abcdefghijklmnopqrstuvwxyz': new_word = first+c+second if new_word in wordList: if new_word in end_q: found = True else: next_q.add(new_word)
paths[new_word].add(word) if swapped else paths[word].add(new_word)
start_q = next_q iflen(start_q) > len(end_q): start_q, end_q = end_q, start_q swapped = not swapped res = [] defbfs(word, cur_path): if word == endWord: cur_path.append(word) res.append(cur_path[::]) return else: for parent in paths[word]: bfs(parent, cur_path+[word]) bfs(beginWord, []) return res
Create a set that contains wordList, and a dictionary layer that contains <endWord, all possible sequences> pairs. While layer is not empty, we loop through all keys, and see if the endWord is in it. Then, we loop through each character in the word, trying to replace it with every alphabet. If this new word is valid, we create an entry in the new dictionary, and the values are formed by the values of the old word. Outside of this for loop, we remove the keys in the new dictionary so that they will not be reused, and we assign the new dictionary to the old one.
deffindLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]: if endWord notin wordList: return [] wordSet = set(wordList) layer = {} layer[beginWord] = [[beginWord]] while layer: new_l = collections.defaultdict(list) for word in layer: if word == endWord: return layer[word] for i inrange(len(word)): for c in'abcdefghijklmnopqrstuvwxyz': new_word = word[:i] + c + word[i+1:] if new_word in wordSet: # add new word to all sequences and form new layer element new_l[new_word] += [j + [new_word] for j in layer[word]] wordSet -= set(new_l.keys()) layer = new_l return []
934. Shortest Bridge
First use DFS find one island, change all cells to 2 in order to differentiate from the other island, and also add the position to the stack. Get the size of the stack, while it is not empty, pop one position from the stack and explore in four directions, if it is not valid or still the same island (==2), continue. If it is 1, then it means we found the island, return the number of steps. Otherwise, we set this empty land to be 2, and append the position to the level. When we run out of current possible movements, we increment the steps by 1, and assign level to the stack.
defshortestDistance(self, grid: List[List[int]]) -> int: ifnot grid: return -1 # create a list of buildings and a set of empty lands for easy search later build, land = [], set() for x,y in itertools.product(range(len(grid)), range(len(grid[0]))): if grid[x][y] == 0: land.add((x,y)) elif grid[x][y] == 1: build.append((x,y)) # if there is no empty land available, we can't proceed further ifnot land: return -1 # create a dictionary where each land's position tuple is a key and its value # is a list of length equal to the number of buildings we found above. # This list is populated with Inf, to start with, that will be updated by the # shortest distance between the land and building pair, found by BFS routine below. d = {x: [float('inf')]*len(build) for x in land} # BFS for a given building's location defBFS(loc): x, y = build[loc] # (x,y) is building's location on the grid and 0 is the starting distance q = collections.deque([(x,y,0)]) while q: x, y, dist = q.popleft() for i, j in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]: # we are interested in only traversing lands whose recorded distance from # current building is more than dist+1. We update their distance and include # these lands in our queue for the next layer, if that's the case if (i,j) in land and d[(i,j)][loc] > dist + 1: d[(i,j)][loc] = dist + 1 q.append((i,j,dist+1)) # run BFS routine for all buildings for loc inrange(len(build)): BFS(loc) # compute the shortest distance to all buildings for each empty land min_dist = min(sum(d[x]) for x in land) return -1if min_dist == float('inf') else min_dist
1293. Shortest Path in a Grid with Obstacles Elimination
Use BFS like a sonar to explore all possible next steps, and once target is in the explored area, meaning we find a solution. Use a queue to store candidates and a set to store visited positions. Keep track of total steps taken, current position, and quota. If quota is still valid and position is not visited, add it to visited set and the queue.
Run time: $O(nk)$, where n is the number of cells in the grid, k is the quota, space: $O(nk)$
defwallsAndGates(self, rooms: List[List[int]]) -> None: """ Do not return anything, modify rooms in-place instead. """ ifnot rooms: return [] # Initialize the queue with all 0s R, C = len(rooms), len(rooms[0]) q = collections.deque() for r inrange(R): for c inrange(C): if rooms[r][c] == 0: q.append((r, c))
while q: r, c = q.popleft() for x, y in [(-1, 0), (1, 0), (0, -1), (0, 1)]: if0 <= r+x < R and0 <= c+y < C and rooms[r+x][c+y] > rooms[r][c]: rooms[r+x][c+y] = rooms[r][c] + 1 q.append((r+x, c+y))
1368. Minimum Cost to Make at Least One Valid Path in a Grid
First, we start from (0,0) and explore other nodes based on direction. We set the corresponding position in the dp matrix to be 0 if it is not visited, and we also add this node to a queue.
Next, we iterate the queue, for each node, we try changing it to all three other directions, and call helper function recursively so that unvisited position are added to queue.
defminCost(self, grid: List[List[int]]) -> int: m,n = len(grid), len(grid[0]) queue = deque() dp = [[float('inf')]*n for _ inrange(m)] dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] k = 0 defdfs(i, j): ifnot (0 <= i < m and0 <= j < n) or dp[i][j] != float('inf'): return dp[i][j] = k queue.append((i, j)) next_dir = grid[i][j] - 1 x, y = dirs[next_dir] di, dj = i+x, j+y dfs(di, dj) dfs(0, 0) while queue: k += 1 size = len(queue) for _ inrange(size): i, j = queue.popleft() for x, y in dirs: di, dj = i+x, j+y dfs(di, dj) return dp[-1][-1]
834. Sum of Distances in Tree
Key idea: Once we get a node as a base and get the sum of distances, when we move the root/base to another node, nodes that are closer to the previous base will have less distance, nodes that are far from the previous base will have more distance. formula = parent_sum - #closer nodes + #further nodes
Create a dictionary that stores the edges in undirected graph. Maintain one array that tracks the number of closer nodes, and one answer array, and one set that tracks visited nodes.
In the first dfs, we set 0 as the base node, to find out the number of total distances between each node. For each unvisited child, we call function recursively to get the number of child nodes, then update the number of closer nodes and the first entry of the answer array. After the for loop ends, we update the closer_nodes_count to be the number of closer nodes, and return it. This way we could fill in the closer_nodes_count array so that next dfs would help get the total distances for each node as root.
Second dfs implement the formula and call it recursively. https://www.youtube.com/watch?v=dkPYrvq5EmY
defsumOfDistancesInTree(self, n: int, edges: List[List[int]]) -> List[int]: # formula = parent_sum - #closer nodes + #further nodes graph = defaultdict(list) for u, v in edges: graph[u].append(v) graph[v].append(u) closer_nodes_count = [0] * n ans = [0] * n
defdfs(cur): num_closer_nodes = 1 for child in graph[cur]: if child notin seen: seen.add(child) child_nodes_count = dfs(child) num_closer_nodes += child_nodes_count ans[0] += child_nodes_count closer_nodes_count[cur] = num_closer_nodes return num_closer_nodes seen = set() seen.add(0) dfs(0) # populating closer_nodes_count and asn[0] defdfs2(cur): for child in graph[cur]: # use parent to calculate the answer for child nodes if child notin seen: seen.add(child) ans[child] = ans[cur] - closer_nodes_count[child] + (n-closer_nodes_count[child]) dfs2(child) seen = set() seen.add(0) dfs2(0) # use ans[0] as base to populate all other nodes' answer return ans
133. Clone Graph
Create a dictionary to store <original node, duplicate node>, then call helper function to loop through all neighbors. If it exists in the dictionary, we just add it to the neighbor list. Otherwise, we duplicate it, add it to the dictionary, append it to neighbor list, call function recursively.
defminFlips(self, mat: List[List[int]]) -> int: defflip(i,j): dirs = [(i,j),(i,j+1),(i,j-1),(i+1,j),(i-1,j)] for x,y in dirs: if0 <= x < m and0 <= y < n: mat[x][y] ^= 1 defallZeros(): returnsum(sum(row) for row in mat) == 0 defhelper(pos, moves): if allZeros(): self.res=min(self.res,moves) return ifnot pos: return for i inrange(len(pos)): x,y = pos[i] flip(x,y) helper(pos[i+1:], moves+1) flip(x,y) m,n = len(mat), len(mat[0]) self.res = float('inf') pos = [(x,y) for x inrange(len(mat)) for y inrange(len(mat[0]))] helper(pos, 0) if self.res != float('+inf'): return self.res else: return -1
785. Is Graph Bipartite?
We’ll keep an array (or hashmap) to lookup the color of each node: color[node]. The colors could be 0, 1, or uncolored (-1 or null).
We should be careful to consider disconnected components of the graph, by searching each node. For each uncolored node, we’ll start the coloring process by doing a depth-first-search on that node. Every neighbor gets colored the opposite color from the current node. If we find a neighbor colored the same color as the current node, then our coloring was impossible.
Run time: $O(n+e)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defisBipartite(self, graph: List[List[int]]) -> bool: color = {} for node inrange(len(graph)): if node notin color: stack = [node] color[node] = 0 while stack: node = stack.pop() for neighbor in graph[node]: if neighbor notin color: stack.append(neighbor) color[neighbor] = color[node] ^ 1 elif color[neighbor] == color[node]: returnFalse returnTrue
417. Pacific Atlantic Water Flow
> Use two matrices of the same size to determine whether one position is reachable from the top left and bottom right corner. If the cell from both matrices are true, then it is reachable to both corners. In the helper function, if this position is already marked true, then return. Otherwise, marked it as true, and explore the four neighbors.
defpacificAtlantic(self, heights): """ :type heights: List[List[int]] :rtype: List[List[int]] """ dirs = [0, 1, 0, -1, 0] m, n = len(heights), len(heights[0]) defdfs(heights, reach, x, y): if reach[x][y]: return reach[x][y] = True for i inrange(4): next_x, next_y = x+dirs[i], y+dirs[i+1] if0 <= next_x < m and0 <= next_y < n and heights[x][y] <= heights[next_x][next_y]: dfs(heights, reach, next_x, next_y) ifnot heights orlen(heights[0]) == 0: return [] reachable_p = [[Falsefor _ inrange(m)] for _ inrange(n)] reachable_a = [[Falsefor _ inrange(m)] for _ inrange(n)] ans = [] for i inrange(m): dfs(heights, reachable_p, i, 0) dfs(heights, reachable_a, i, n-1) for i inrange(len(heights[0])): dfs(heights, reachable_p, 0, i) dfs(heights, reachable_a, m-1, i) for i inrange(m): for j inrange(n): if reachable_p[i][j] and reachable_a[i][j]: ans.append([i,j]) return ans
547. Number of Provinces
In order to find the number of connected components in an undirected graph, one of the simplest methods is to make use of Depth First Search starting from every node. We make use of visitedvisited array of size N(M is of size NxN).
deffindCircleNum(self, isConnected): """ :type isConnected: List[List[int]] :rtype: int """ defdfs(isConnected, i, visited): visited.add(i) for j inrange(len(isConnected)): if isConnected[i][j] == 1and j notin visited: dfs(isConnected, j, visited) ifnot isConnected: return0 visited = set() count = 0 for i inrange(len(isConnected)): if i notin visited: dfs(isConnected, i, visited) count += 1 return count
695. Max Area of Island
> in the helper function, change the grid value from 1 to 0, then call the function again for the four directions plus 1.
Run time: $O(n^2)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defmaxAreaOfIsland(self, grid): """ :type grid: List[List[int]] :rtype: int """ defdfs(grid, i, j): if i < 0or i >= len(grid) or j < 0or j >= len(grid[0]) or grid[i][j] == 0: return0 grid[i][j] = 0 return1+dfs(grid,i+1,j)+dfs(grid,i-1,j)+dfs(grid,i,j+1)+dfs(grid,i,j-1) ifnot grid orlen(grid) == 0orlen(grid[0]) == 0: return0 max_area = 0 for i inrange(len(grid)): for j inrange(len(grid[0])): max_area = max(max_area, dfs(grid, i, j)) return max_area
366. Find Leaves of Binary Tree
> Create helper function that gets the height of a node. In the function, use post-order traversal to get the height of the left and right child, then set the max of them + 1 as the height of given node. Put in a dictionary.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12
deffindLeaves(self, root: 'TreeNode') -> 'List[List[int]]': res = [] defdfs(root): ifnot root: return -1 height = max(dfs(root.left), dfs(root.right)) + 1 if height >= len(res): res.append([]) res[height].append(root.val) return height dfs(root) return res
deffindLeaves(self, root): """ :type root: TreeNode :rtype: List[List[int]] """ d = {} defgetHeight(node): if node isNone: return -1 leftH = getHeight(node.left) rightH = getHeight(node.right) currH = max(leftH, rightH)+1 if currH in d: d[currH].append(node.val) else: d[currH] = [node.val] return currH ans = [] getHeight(root) for k, v in d.items(): ans.append(v) return ans
Backtracking
>
Run time: $O()$, space: $O()$
1
465. Optimal Account Balancing
Store non-zero balance to a list, then use backtracking to loop through all unvisited balances (look forward), and if their balance has different sign, update the candidate’s balance, minimize ans by calling backtracking function recursively plus 1. https://www.youtube.com/watch?v=B8pPWgLa2lQ
defminTransfers(self, transactions: List[List[int]]) -> int: debts = defaultdict(int) for f, t, amt in transactions: debts[f] -= amt debts[t] += amt debts_list = [debts[id] foridin debts if debts[id] != 0] n = len(debts_list) defbacktrack(ind): while ind < n and debts_list[ind] == 0: ind += 1 if ind == n: return0 ans = float('inf') for i inrange(ind+1, n): if debts_list[ind] * debts_list[i] < 0: debts_list[i] += debts_list[ind] ans = min(ans, backtrack(ind+1)+1) debts_list[i] -= debts_list[ind] return ans return backtrack(0)
489. Robot Room Cleaner
This solution is based on the same idea as maze solving algorithm called right-hand rule. Go forward, cleaning and marking all the cells on the way as visited. At the obstacle turn right, again go forward, etc. Always turn right at the obstacles and then go forward. Consider already visited cells as virtual obstacles.
Run time: $O(N−M)$, space: $O(N−M)$, where $N$ is a number of cells in the room and $M$ is a number of obstacles.
defremoveInvalidParentheses(self, s: str) -> List[str]: # initialize a set with one element # set is used here in order to avoid duplicate element level = {s} whileTrue: valid = [] for elem in level: if self.isValid(elem): valid.append(elem) if valid: return valid # initialize an empty set new_level = set() # BFS for elem in level: for i inrange(len(elem)): new_level.add(elem[:i] + elem[i + 1:]) level = new_level
defisValid(self,s): count = 0 for c in s: if c == '(': count += 1 elif c == ')': count -= 1 if count < 0: returnFalse return count == 0
defremoveInvalidParentheses(self, s: str) -> List[str]: defisValid(s): stack = [] for i inrange(len(s)): if( s[i] == '(' ): stack.append( (i,'(') ) elif( s[i] == ')' ): if(stack and stack[-1][1] == '('): stack.pop() else: stack.append( (i,')') ) # pushing invalid close braces also returnlen(stack) == 0, stack defdfs( s, left, right): visited.add(s) if left == 0and right == 0and isValid(s)[0]: res.append(s) for i, ch inenumerate(s): if ch != '('and ch != ')': continue# if it is any other char ignore. if (ch == '('and left == 0) or (ch == ')'and right == 0): continue# if left == 0 then removing '(' will only cause imbalance. Hence, skip. if s[:i] + s[i+1:] notin visited: dfs( s[:i] + s[i+1:], left - (ch == '('), right - (ch == ')') ) stack = isValid(s)[1] lc = sum([1for val in stack if val[1] == "("]) # num of left braces rc = len(stack) - lc res, visited = [], set() dfs(s, lc, rc) return res
79. Word Search
Go through each entry, and in the helper function, check edge cases. If word length is zero, then it means this word has been found. Set the current entry to be #, call functions in four directions recursively, then reverse the change, return the result.
Run time: $O(n\cdot 3^L)$, space: $O(L)$, where $n$ is the number of cells in the board, $L$ is the length of the word to be matched, 3 is the number of further choices in the next step
defexist(self, board, word): """ :type board: List[List[str]] :type word: str :rtype: bool """ # check whether can find word, start at (i,j) position defdfs(board, i, j, word): iflen(word) == 0: # all the characters are checked returnTrue if i<0or i>=len(board) or j<0or j>=len(board[0]) or word[0]!=board[i][j]: returnFalse tmp = board[i][j] # first character is found, check the remaining part board[i][j] = "#"# avoid visit agian # check whether can find "word" along one direction res = dfs(board, i+1, j, word[1:]) or dfs(board, i-1, j, word[1:]) \ or dfs(board, i, j+1, word[1:]) or dfs(board, i, j-1, word[1:]) board[i][j] = tmp return res
ifnot board: returnFalse for i inrange(len(board)): for j inrange(len(board[0])): if dfs(board, i, j, word): returnTrue returnFalse
77. Combinations
in the helper function, check whether current combination length equals to k, then append to the answer list. In the for loop, pass the array that skips the current element, the path that combines this element.
Run time: $O(k {n \choose k})$, space: $O({n \choose k})$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
defcombine(self, n, k): """ :type n: int :type k: int :rtype: List[List[int]] """ defdfs(nums, k, path, ans): iflen(path) == k: ans.append(path) return for i inrange(len(nums)): dfs(nums[i+1:], k, path+[nums[i]], ans) ans = [] dfs(list(range(1, n+1)), k, [], ans) return ans
46. Permutations
> `[:]` makes a deep copy of the list. If you passed back the original list, it would be modified by future swaps. Otherwise the reference to the array is appended rather than the actual list.
Run time: $O(\sum_{k=1}^n P(n, k))$, space: $O(n!)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defpermute(self, nums): """ :type nums: List[int] :rtype: List[List[int]] """ defbacktracking(nums, level, ans): if level == len(nums)-1: ans.append(nums[:]) return for i inrange(level, len(nums)): nums[i], nums[level] = nums[level], nums[i] backtracking(nums, level+1, ans) nums[i], nums[level] = nums[level], nums[i] ans = [] backtracking(nums, 0, ans) return ans
1 2 3 4 5 6 7 8 9 10
defpermute(self, nums): iflen(nums) == 1: return [nums] result = [] for i inrange(len(nums)): others = nums[:i] + nums[i+1:] other_permutations = self.permute(others) for permutation in other_permutations: result.append([nums[i]] + permutation) return result
defaddOperators(self, num, target): """ :type num: str :type target: int :rtype: List[str] """ defbacktracking(idx=0, path='', value=0, prev=None): if idx == len(num) and value == target: rtn.append(path) return for i inrange(idx+1, len(num)+1): temp = int(num[idx:i]) if i == idx+1or (i > idx+1and num[idx] != '0'): ifnot prev: backtracking(i, num[idx:i], temp, temp) else: backtracking(i, path+'+'+num[idx:i], value+temp, temp) backtracking(i, path+'-'+num[idx:i], value-temp, -temp) backtracking(i, path+'*'+num[idx:i], value-prev+prev*temp, prev*temp) rtn = [] backtracking() return rtn
Linked List & Array
>
Run time: $O()$, space: $O()$
1
54. Spiral Matrix
Maintain four variables - top, bottom, left, right, while they are valid, run four for loops and update them. In the end, if the output length is less than actual cells, run a nested for loop for the rest of the bounds.
defspiralOrder(self, matrix: List[List[int]]) -> List[int]: height = len(matrix) width = len(matrix[0]) top = 0 bottom = height - 1 left = 0 right = width - 1 ans = [] while top < bottom and left < right: for col inrange(left, right): ans.append(matrix[top][col]) for row inrange(top, bottom): ans.append(matrix[row][right]) for col inrange(right, left, -1): ans.append(matrix[bottom][col]) for row inrange(bottom, top, -1): ans.append(matrix[row][left]) top += 1 bottom -= 1 left += 1 right -= 1 # If a matrix remain inside it is either a 1xn or a mx1 # a linear scan will return the same order as spiral for these iflen(ans) < height*width: for row inrange(top, bottom+1): for col inrange(left, right+1): ans.append(matrix[row][col]) return ans
284. Peeking Iterator
Store the next value outside the iterator. When next is called return the stored value and populate with next value from iterator.
# Your PeekingIterator object will be instantiated and called as such: # iter = PeekingIterator(Iterator(nums)) # while iter.hasNext(): # val = iter.peek() # Get the next element but not advance the iterator. # iter.next() # Should return the same value as [val].
900. RLE Iterator
Keep track of the index, and increment 2 at a time once the number has been used up.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classRLEIterator: def__init__(self, A: List[int]): self.A = A self.index = 0 defnext(self, n: int) -> int: while self.index < len(self.A): if n <= self.A[self.index]: self.A[self.index] -= n return self.A[self.index + 1] n -= self.A[self.index] self.index += 2 return -1
23. Merge k Sorted Lists
Put <value, node index in array, node> to a heap, each time pop the smallest element, set the next element to be the next element, then push the next element back to the heap.
Run time: $O(n\log k)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defmergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: curr = head = ListNode(0) q = [] for l_id, l inenumerate(lists): if l: heapq.heappush(q, (l.val, l_id, l)) while q: _, l_id, temp = heapq.heappop(q) curr.next = temp curr = curr.next if curr.next: heapq.heappush(q, (curr.next.val, l_id, curr.next)) return head.next
1877. Minimize Maximum Pair Sum in Array
Sort the array, then add from start and end, keep track of the max sum.
defshuffle(self) -> List[int]: ans = self.nums[:] # copy list for i inrange(len(ans)-1, 0, -1): # start from end j = random.randrange(0, i+1) # generate random index ans[i], ans[j] = ans[j], ans[i] # swap return ans
1509. Minimum Difference Between Largest and Smallest Value in Three Moves
Only keep track of the largest and smallest 3 elements.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defminDifference(self, nums: List[int]) -> int: iflen(nums) < 5: return0 maxV, minV = [-float('inf')] * 4, [float('inf')] * 4 for n in nums: if n > maxV[0]: maxV[0] = n for i inrange(0, 3): if maxV[i] > maxV[i + 1]: maxV[i], maxV[i + 1] = maxV[i + 1], maxV[i] if n < minV[0]: minV[0] = n for i inrange(0, 3): if minV[i] < minV[i + 1]: minV[i], minV[i + 1] = minV[i + 1], minV[i] returnmin(maxV[i] - minV[3 - i] for i inrange(4))
sort the array and compute the minimum over the three pair difference.
Use a pointer to memorize the starting index of the candidate subarray. If we run out of k, we move the pointer until it’s pointing to zero, and then increment by 1 to skip this zero and include the zero at the end index.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12
deflongestOnes(self, nums: List[int], k: int) -> int: n, ans, l = len(nums), 0, 0 for r inrange(n): if nums[r] == 0: # try to pick current 0 if k == 0: # if window already picked k zeros, pop 1 from left and pick this while nums[l] != 0 : l += 1 l += 1 else: k-= 1# otherwise pick it and decrement k ans = max(ans, r - l + 1) # update ans as max window size till now return ans
977. Squares of a Sorted Array
Fill in the output array backwards. Use two pointers, if the abs value of number at right pointer is greater, then set the variable and reduce right pointer.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defsortedSquares(self, nums: List[int]) -> List[int]: n = len(nums) result = [0] * n left = 0 right = n - 1 for i inrange(n - 1, -1, -1): ifabs(nums[left]) < abs(nums[right]): square = nums[right] right -= 1 else: square = nums[left] left += 1 result[i] = square * square return result
896. Monotonic Array
Use two boolean and return if they are both false or not.
for i inrange(len(nums) - 1): if nums[i] > nums[i+1]: increasing = False if nums[i] < nums[i+1]: decreasing = False
return increasing or decreasing
138. Copy List with Random Pointer
Instead of a separate dictionary to keep the old node --> new node mapping, we can tweak the original linked list and keep every cloned node next to its original node. This interleaving of old and new nodes allows us to solve this problem without any extra space.
defcopyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': ifnot head: return head
ptr = head while ptr: # Cloned node new_node = Node(ptr.val, None, None)
# Inserting the cloned node just next to the original node. # If A->B->C is the original linked list, # Linked list after weaving cloned nodes would be A->A'->B->B'->C->C' new_node.next = ptr.next ptr.next = new_node ptr = new_node.next
ptr = head
# Now link the random pointers of the new nodes created. # Iterate the newly created list and use the original nodes random pointers, # to assign references to random pointers for cloned nodes. while ptr: ptr.next.random = ptr.random.nextif ptr.random elseNone ptr = ptr.next.next
# Unweave the linked list to get back the original linked list and the cloned list. # i.e. A->A'->B->B'->C->C' would be broken to A->B->C and A'->B'->C' ptr_old_list = head # A->B->C ptr_new_list = head.next# A'->B'->C' head_new = head.next while ptr_old_list: ptr_old_list.next = ptr_old_list.next.next ptr_new_list.next = ptr_new_list.next.nextif ptr_new_list.nextelseNone ptr_old_list = ptr_old_list.next ptr_new_list = ptr_new_list.next return head_new
defsortList(self, head: Optional[ListNode]) -> Optional[ListNode]: ifnot head ornot head.next: return head fast, slow = head.next, head while fast and fast.next: fast = fast.next.next slow = slow.next start = slow.next slow.next = None l, r = self.sortList(head), self.sortList(start) return self.merge(l, r) defmerge(self, l, r): ifnot l ornot r: return l or r if l.val > r.val: l, r = r, l # get the return node "head" head = pre = l l = l.next while l and r: if l.val < r.val: pre.next = l l = l.next else: pre.next = r r = r.next pre = pre.next # l and r at least one is None pre.next = l or r return head
defsortList(self, head: Optional[ListNode]) -> Optional[ListNode]: defgetsize(head): c = 0 while head: head = head.next c += 1 return c defsplit(head, size): tail = head for _ inrange(size-1): ifnot tail: returnNone tail = tail.next ifnot tail: returnNone next_head, tail.next = tail.next, None return next_head defmerge(l1, l2, newtail): while l1 and l2: if l1.val <= l2.val: newtail.next = l1 l1 = l1.next else: newtail.next = l2 l2 = l2.next newtail = newtail.next newtail.next = l1 or l2 while newtail.next: newtail = newtail.next return newtail length = getsize(head) newhead = ListNode(next=head) size = 1 while size < length: newtail, cur = newhead, newhead.next while cur: h1 = cur h2 = split(h1, size) cur = split(h2, size) newtail = merge(h1, h2, newtail) size *= 2 return newhead.next
19. Remove Nth Node From End of List
One pass. The first pointer advances the list by n+1n+1 steps from the beginning, while the second pointer starts from the beginning of the list. Now, both pointers are exactly separated by nn nodes apart. We maintain this constant gap by advancing both pointers together until the first pointer arrives past the last node. The second pointer will be pointing at the nnth node counting from the last. We relink the next pointer of the node referenced by the second pointer to point to the node’s next next node.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11
defremoveNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: fast = slow = head for _ inrange(n): fast = fast.next ifnot fast: return head.next while fast.next: fast = fast.next slow = slow.next slow.next = slow.next.next return head
Two pass. First time calculate the length. Second time reduce the count till 0, and remove that node.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
defremoveNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: ans = ListNode(0) ans.next = head ptr = head count = 0 while ptr: count += 1 ptr = ptr.next count -= n ptr = ans while count > 0: count -= 1 ptr = ptr.next ptr.next = ptr.next.next return ans.next
83. Remove Duplicates from Sorted List
Recursion. Set the next element to be the function call. Return head.next if it is the same as head, otherwise return head.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8
defdeleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: if head and head.next: head.next = self.deleteDuplicates(head.next) if head.next.val == head.val: return head.next else: return head return head
Iterative. Remember to put moving head in the else statement.
1 2 3 4 5 6 7 8 9 10 11 12
defdeleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: ifnot head ornot head.next: return head cur = head while cur and cur.next: if cur.val == cur.next.val: cur.next = cur.next.next else: cur = cur.next return head
234. Palindrome Linked List
Recursion. If we iterate the nodes in reverse using recursion, and iterate forward at the same time using a variable outside the recursive function, then we can check whether or not we have a palindrome.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defisPalindrome(self, head: Optional[ListNode]) -> bool: self.front_ptr = head defrecursive_check(node): if node: ifnot recursive_check(node.next): returnFalse if self.front_ptr.val != node.val: returnFalse self.front_ptr = self.front_ptr.next returnTrue return recursive_check(head)
Copy into Array List and then Use Two Pointer Technique.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6
defisPalindrome(self, head: Optional[ListNode]) -> bool: vals = [] while head: vals.append(head.val) head = head.next return vals == vals[::-1]
160. Intersection of Two Linked Lists
If we say that cc is the shared part, aa is exclusive part of list A and bb is exclusive part of list B, then we can have one pointer that goes over a + c + b and the other that goes over b + c + a. Have a look at the diagram below, and this should be fairly intuitive.
Use set to put all nodes in B. Then loop through A once and see if a node is already in the set.
Run time: $O(m+n)$, space: $O(m)$
1 2 3 4 5 6 7 8 9 10 11 12
defgetIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: s = set() while headB: s.add(headB) headB = headB.next
while headA: if headA in s: return headA headA = headA.next returnNone
Brute force. For each node in A, loop through B and see if two nodes are the same.
Run time: $O(mn)$, space: $O(1)$
1 2 3 4 5 6 7 8 9
defgetIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: while headA: p = headB while p: if headA == p: return headA p = p.next headA = headA.next returnNone
24. Swap Nodes in Pairs
Recursive. In every function call we take out two nodes which would be swapped and the remaining nodes are passed to the next recursive call. The reason we are adopting a recursive approach here is because a sub-list of the original list would still be a linked list and hence, it would adapt to our recursive strategy.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7
defswapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]: if head and head.next: temp = head.next head.next = self.swapPairs(temp.next) temp.next = head return temp return head
Iterative. We break the linked list into pairs by jumping in steps of two. The only difference is, unlike recursion, we swap the nodes on the go. After swapping a pair of nodes, say A and B, we need to link the node B to the node that was right before A. To establish this linkage we save the previous node of node A in prevNode.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defswapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]: ifnot head ornot head.next: return head ans = ListNode(0) ans.next = head ptr = ans while head and head.next: first = head second = head.next ptr.next = second first.next = second.next second.next = first ptr = first head = first.next return ans.next
21. Merge Two Sorted Lists
Iterative.
Run time: $O(m+n)$, space: $O(m+n)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defmergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: ans = ListNode(0) ptr = ans while list1 and list2: if list1.val < list2.val: ans.next = list1 list1 = list1.next else: # list1 > list2 ans.next = list2 list2 = list2.next ans = ans.next ans.next = list1 or list2 return ptr.next
Recursion. We can recursively define the result of a merge operation on two lists as the following (avoiding the corner case logic surrounding empty lists):
206. Reverse Linked List
> Iterative solution. Use one variable prev and one called curr that points to head. While curr is not None, use another variable to store the next node, set the next node of curr to be prev, set prev to be curr, in the end set curr to be the temp variable.
The recursive version is slightly trickier and the key is to work backwards. Assume that the rest of the list had already been reversed, now how do I reverse the front part? Let’s assume the list is: n1 → … → nk-1 → nk → nk+1 → … → nm → Ø
Assume from node nk+1 to nm had been reversed and you are at node nk.whilifans.list
n1 → … → nk-1 → nk → nk+1 ← … ← nm
We want nk+1’s next node to point to nk.
So, nk.next.next = nk;
Be very careful that n1’s next must point to Ø. If you forget about this, your linked list has a cycle in it. This bug could be caught if you test your code with a linked list of size 2.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8
defreverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: ifnot head ornot head.next: return head temp_next = self.reverseList(head.next) head.next.next = head head.next = None return temp_next
560. Subarray Sum Equals K
> Create a dict to store . In the loop, find if cumulative sum - k is in the dict, then add the value to the count.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defsubarraySum(self, nums, k): """ :type nums: List[int] :type k: int :rtype: int """ count = 0 sums = 0# cumulative sum d = dict() d[0] = 1 for i inrange(len(nums)): sums += nums[i] count += d.get(sums-k,0) d[sums] = d.get(sums,0) + 1 return(count)
1570. Dot Product of Two Sparse Vectors
> Store the index and nonzero values in a dictionary, and check if the keys in another dict is in this dict as well, the compute the product.
Run time: $O(n)$, space: $O(L)$, where $L$ is the number of nonzero values
classSparseVector: def__init__(self, nums): """ :type nums: List[int] """ self.d = {} for i, num inenumerate(nums): self.d[i] = num
# Return the dotProduct of two sparse vectors defdotProduct(self, vec): """ :type vec: 'SparseVector' :rtype: int """ ans = 0 for i, num in self.d.items(): if i in vec.d: ans += vec.d[i] * num return ans
# Your SparseVector object will be instantiated and called as such: # v1 = SparseVector(nums1) # v2 = SparseVector(nums2) # ans = v1.dotProduct(v2)
Stack, Queue
>
Run time: $O()$, space: $O()$
1
1296. Divide Array in Sets of K Consecutive Numbers
defisPossibleDivide(self, nums: List[int], k: int) -> bool: c = collections.Counter(nums) for i insorted(c): if c[i] > 0: for j inrange(k)[::-1]: c[i + j] -= c[i] if c[i + j] < 0: returnFalse returnTrue
1 2 3 4 5 6 7 8 9 10
defisPossibleDivide(self, nums: List[int], k: int) -> bool: c = collections.Counter(nums) start = collections.deque() last_checked, opened = -1, 0 for i insorted(c): if opened > c[i] or opened > 0and i > last_checked + 1: returnFalse start.append(c[i] - opened) last_checked, opened = i, c[i] iflen(start) == k: opened -= start.popleft() return opened == 0
defevalRPN(self, tokens: List[str]) -> int: stack = [] for t in tokens: if t notin"+-*/": stack.append(int(t)) else: r, l = stack.pop(), stack.pop() if t == "+": stack.append(l+r) elif t == "-": stack.append(l-r) elif t == "*": stack.append(l*r) else: stack.append(int(float(l)/r)) return stack.pop()
defremoveDuplicates(self, s: str, k: int) -> str: stack = [['#', 0]] for c in s: if stack[-1][0] == c: stack[-1][1] += 1 if stack[-1][1] == k: stack.pop() else: stack.append([c, 1]) return''.join(c * k for c, k in stack)
239. Sliding Window Maximum
Monotonic decreasing queue.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
defmaxSlidingWindow(self, nums: List[int], k: int) -> List[int]: ans = [] desc_q = collections.deque() for i, num inenumerate(nums): # pop out-of-bound index if i >= k and desc_q[0] == i-k: desc_q.popleft() # pop index where elements are smaller than current num while desc_q and nums[desc_q[-1]] < num: desc_q.pop() desc_q.append(i) if i >= k-1: ans.append(nums[desc_q[0]]) return ans
Use max heap (i.e. a min heap that stores <-num, index>). Each time we need to pop the element if the current top element index exceeds the k bound.
Run time: $O(n\log n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10
defmaxSlidingWindow(self, nums: List[int], k: int) -> List[int]: ans = [] pq = [] for i, num inenumerate(nums): while pq and i >= k and pq[0][1] <= i-k: heapq.heappop(pq) heapq.heappush(pq, (-num, i)) if i >= k-1: ans.append(-pq[0][0]) return ans
1499. Max Value of Equation
Monotonic decreasing queue that stores <y-x, x> in descending order. Pop elements that does not satisfy the |x-xj| <= k restriction, i.e., xj >= x-k, then update the max. Then, pop elements that has y-x smaller than or equal to the current y-x, since we are not going to use them.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11
deffindMaxValueOfEquation(self, points: List[List[int]], k: int) -> int: q = collections.deque() ans = -float('inf') for x, y in points: while q and q[0][1] < x - k: q.popleft() if q: ans = max(ans, q[0][0] + y + x) while q and q[-1][0] <= y - x: q.pop() q.append([y - x, x]) return ans
Use max heap (in python, create heap and store the negative value) to store y-x for each point. While the x value of current point in the max heap has difference greater than k, we pop it from the heap. Then we get the sum of current x+y, and the y-x from the heap.
Run time: $O(n\log n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10
deffindMaxValueOfEquation(self, points: List[List[int]], k: int) -> int: q = [] # max heap that stores <negative of y-x, x> ans = -float('inf') for x, y in points: while q and x - q[0][1] > k: heapq.heappop(q) if q: ans = max(ans, -q[0][0] + (x+y)) heapq.heappush(q, (-(y-x), x)) return ans
394. Decode String
Use one variable to keep track of number and string. If we see [, we add string and num to stack and reset them. If we see ], we poll num and string and update string. If we see a digit, we update num. Otherwise, we append char to string.
defdecodeString(self, s: str) -> str: stack = [] num = 0 string = "" for c in s: if c == '[': stack.append(string) stack.append(num) string = "" num = 0 elif c == ']': oldNum = stack.pop() oldString = stack.pop() string = oldString + oldNum * string elif c.isdigit(): num = num * 10 + int(c) else: string += c return string
1047. Remove All Adjacent Duplicates In String
Current string character is equal to the last element in stack? Pop that last element out of stack.
Current string character is not equal to the last element in stack? Add the current character into stack.
Run time: $O(n)$, space: $O(n-d)$, where $d$ is the number of dup char.
1 2 3 4 5 6 7 8
defremoveDuplicates(self, s: str) -> str: stack = [] for ch in s: if stack and ch == stack[-1]: stack.pop() else: stack.append(ch) return"".join(stack)
716. Max Stack
Create a stack and put [value, current index of the max].
Run time: $O(n)$ for popMax, $O(1)$ for all other calls, space: $O(n)$
defpush(self, x: int) -> None: if self.stack and x >= self.stack[self.stack[-1][1]][0]: i = len(self.stack) # index of max else: if self.stack: i = self.stack[-1][1] else: i = 0 self.stack.append((x, i))
defpopMax(self) -> int: index = self.stack[-1][1] # index where the max exists result = self.stack[index][0] # max value to return if index > 0: new_max = self.stack[self.stack[index-1][1]][0] else: new_max = -float('inf') # Scan the stack starting at 'index' to recompute the max values and shift all # values to the left by one: for i inrange(index, len(self.stack)-1): if self.stack[i+1][0] >= new_max: new_max = self.stack[i+1][0] self.stack[i] = (self.stack[i+1][0], i) else: self.stack[i] = (self.stack[i+1][0], self.stack[i-1][1]) self.stack.pop() return result
defcalculate(self, s: str) -> int: defgetSubExpr(s): left = 1 x = [] while left != 0: x.append(next(s)) if x[-1] == '(': left += 1 elif x[-1] == ')': left -= 1 # print(x[:-1]) return''.join(x[:-1])
s = iter(re.findall('\d+|\S', s)) operand, sign = 0, 1 total = 0 for token in s: # print(token, operand, sign, total) if token in'+-': total += sign * operand sign = [1, -1][token == '-'] elif token in'/*': n = next(s) n = self.calculate(getSubExpr(s)) if n == '('elseint(n) operand = operand*n if token == '*'else operand/n else: operand = self.calculate(getSubExpr(s)) if token == '('elseint(token) returnint(total + sign * operand)
503. Next Greater Element II
This approach makes use of a stack. This stack stores the indices of the appropriate elements from numsnums array. The top of the stack refers to the index of the Next Greater Element found so far. We store the indices instead of the elements since there could be duplicates in the numsnums array. The description of the method will make the above statement clearer.
We start traversing the numsnums array from right towards the left. For an element nums[i] encountered, we pop all the elements stack[top]stack[top] from the stack such that num[stack[top]] ≤ nums[i]. We continue the popping till we encounter a stack[top] satisfying nums[stack[top]]>nums[i]. Now, it is obvious that the current stack[top] only can act as the Next Greater Element for nums[i](right now, considering only the elements lying to the right of nums[i]).
If no element remains on the top of the stack, it means no larger element than nums[i]nums[i] exists to its right. Along with this, we also push the index of the element just encountered(nums[i]), i.e. ii over the top of the stack, so that nums[i](or stack[top]) now acts as the Next Greater Element for the elements lying to its left.
We go through two such passes over the complete nums array. This is done so as to complete a circular traversal over the nums array. The first pass could make some wrong entries in the res array since it considers only the elements lying to the right of nums[i], without a circular traversal. But, these entries are corrected in the second pass.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defnextGreaterElements(self, nums: List[int]) -> List[int]: stack, r = [], [-1] * len(nums) for i inrange(len(nums)): while stack and (nums[stack[-1]] < nums[i]): r[stack.pop()] = nums[i] stack.append(i) for i inrange(len(nums)): while stack and (nums[stack[-1]] < nums[i]): r[stack.pop()] = nums[i] if stack == []: break return r
739. Daily Temperatures
At each index currDay, check if the current day is the hottest one seen so far. If it is, update hottest and move on. Otherwise, do the following:
Initialize a variable days = 1 because the next warmer day must be at least one day in the future.
While temperatures[currDay + days] <= temperatures[currDay]:
Add answer[currDay + days] to days. This effectively jumps directly to the next warmer day.
Set answer[currDay] = days.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
defdailyTemperatures(self, temperatures: List[int]) -> List[int]: n = len(temperatures) hot = 0 ans = [0] * n for curr_day inrange(n-1,-1,-1): temp = temperatures[curr_day] if temp >= hot: hot = temp continue days = 1 while temperatures[curr_day+days] <= temp: days += ans[curr_day+days] ans[curr_day] = days return ans
If the stack is not empty, that means there are previous days for which we have not yet seen a warmer day.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10
defdailyTemperatures(self, temperatures: List[int]) -> List[int]: n = len(temperatures) ans = [0] * n stack = [] for day, temp inenumerate(temperatures): while stack and temperatures[stack[-1]] < temp: prev_day = stack.pop() ans[prev_day] = day - prev_day stack.append(day) return ans
20. Valid Parentheses
Pop the last element from the stack only if stack is not empty and the last element matches in the pair. In the end, check if stack is empty or not.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defisValid(self, s: str) -> bool: stack = [] iflen(s) == 1: returnFalse for c in s: if stack and c==']'and stack[-1]=='[': stack.pop() elif stack and c=='}'and stack[-1]=='{': stack.pop() elif stack and c==')'and stack[-1]=='(': stack.pop() else: stack.append(c) returnlen(stack)==0
155. Min Stack
> Create a list. Keep track of current element, and the min element so far.
defcalculate(self, s): """ :type s: str :rtype: int """ stack = [] for i inrange(len(s)): a = s[i] if a in"+-*/": stack.append(a) elif a.isnumeric(): j = i+1 while j < len(s) and s[j].isnumeric(): j += 1 a = int(s[i:j]) iflen(stack) > 1and stack[-1] in"*/": op = stack.pop() num = stack.pop() if op == '*': num *= a elif op == '/': num /= a stack.append(num) else: stack.append(a) whilelen(stack) > 2: num2 = stack.pop() op = stack.pop() num1 = stack.pop() if op == '+': num1 += num2 elif op == '-': num1 -= num2 stack.append(num1) return stack.pop()
71. Simplify Path
> First split the string by `/` and remove `.` and empty string, then create a stack to append each element, or pop from the nonempty stack if we see `..`.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defsimplifyPath(self, path): """ :type path: str :rtype: str """ places = [p for p in path.split("/") if p!="."and p!=""] stack = [] for p in places: if p == "..": iflen(stack) > 0: stack.pop() else: stack.append(p) return"/" + "/".join(stack)
735. Asteroid Collision
> Put every positive asteroid to a stack, if the next one is negative, compare if the absolute value is greater, then pop the top element since it will be collapsed. Keep doing this until stack is empty.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defasteroidCollision(self, asteroids): """ :type asteroids: List[int] :rtype: List[int] """ ans = [] for a in asteroids: while ans and a < 0 < ans[-1]: # opposite direction if ans[-1] < -a: # new asteroid is greater than existing one ans.pop() continue elif ans[-1] == -a: ans.pop() break else: ans.append(a) return ans
1762. Buildings With an Ocean View
> Check from right to left and record the max height.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6
deffindBuildings(self, heights: List[int]) -> List[int]: res = collections.deque([len(heights) - 1]) for i inrange(len(heights) - 2, -1, -1): if heights[i] > heights[res[0]]: res.appendleft(i) return res
curr_max = heights[-1] ans = [len(heights)-1] for i inrange(len(heights)-1, -1, -1): curr_h = heights[i] if curr_h > curr_max: ans.append(i) curr_max = curr_h return ans[::-1]
2 Pointers
>
Run time: $O()$, space: $O()$
1
11. Container With Most Water
Keep track of min height and max area, keep updating left or right pointer if it is the smaller one.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defmaxArea(self, height: List[int]) -> int: res = 0 l, r = 0, len(height) - 1 while l < r: h = min(height[l], height[r]) res = max(res, h * (r - l)) if height[l] == h: l += 1 if height[r] == h: r -= 1 return res
1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit
deflongestSubarray(self, nums: List[int], limit: int) -> int: ifnot nums: return0 iflen(nums) == 1: return1 # left and right index for tracing out current subarray l = 0 r = 1 # number out of this may only create problem cur_mx = nums[0] cur_mn = nums[0] # atleast max_l = 1, bcoz [8] = 8-8 = 0 <= limit max_l = 1 # since, it is like sliding window ques, # use while loop with left, right index # with for loop it will be complicated while l <= r and r < len(nums): cur_mx = max(cur_mx, nums[r]) cur_mn = min(cur_mn, nums[r])
if cur_mx - cur_mn <= limit: max_l = max(max_l, r - l + 1) else: if nums[l] == cur_mx: # need to update cur_mx cur_mx = max(nums[l + 1:r + 1]) # inclusive r # using if & not elif bcoz- nums[l] == cur_mn == cur_mx if nums[l] == cur_mn: # need to update cur_mn cur_mn = min(nums[l + 1:r + 1]) l += 1 r += 1 return max_l
deffindLengthOfShortestSubarray(self, arr: List[int]) -> int: n = len(arr) s, e = 0, n-1 while s+1<n and arr[s]<=arr[s+1]: s += 1 if s == n-1: return0 while e-1>=0and arr[e-1]<=arr[e]: e-=1 if e==0: return n-1 result = min(n-1-s,e) i, j = 0, e while i<=s and j<n: if arr[i]<=arr[j]: result = min(result,j-i-1) # j- i -1 will be the length we want to delete i+=1 else: j+=1 return result
defcanTransform(self, start: str, end: str) -> bool: iflen(start) != len(end): returnFalse A = [(s, idx) for idx, s inenumerate(start) if s == 'L'or s == 'R'] B = [(e, idx) for idx, e inenumerate(end) if e == 'L'or e == 'R'] iflen(A) != len(B): returnFalse for (s, i), (e, j) inzip(A, B): if s != e: returnFalse if s == 'L': if i < j: returnFalse if s == 'R': if i > j: returnFalse returnTrue
632. Smallest Range Covering Elements from K Lists
Create a heap that stores <first element, index of array, index of upper bound element, default 0> for all arrays. Initialize the right pointer as the max of first elements in all arrays. As long as the priority queue is not empty, we pop the min element, and update the range if the new range is smaller. If the index of upper bound element reaches the end, we just return the current stored bound. Since we have popped one element from pq, we need to put the next element in the same array back, and we also need to update the upper bound. https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/discuss/104904/Python-Heap-based-solution
Run time: $O(n\log n)$, space: $O(k)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defsmallestRange(self, nums: List[List[int]]) -> List[int]: pq = [(row[0], i, 0) for i, row inenumerate(nums)] heapq.heapify(pq) ans = -float('inf'), float('inf') right = max(row[0] for row in nums) # min possible element while pq: left, i, j = heapq.heappop(pq) if right - left < ans[1] - ans[0]: # smaller bound ans = left, right if j == len(nums[i])-1: # reach the end of an array return ans v = nums[i][j+1] # next number in the array right = max(right, v) # update upper bound heapq.heappush(pq, (v, i, j+1)) # put new number to pq return ans
1423. Maximum Points You Can Obtain from Cards
Since the sum of k choices are consecutive, we could find the sum of first k elements, then add last element from right, and remove last element from previous sum, keep doing until we get the sum of last k elements. During this process we could get the max sum.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7
defmaxScore(self, cardPoints: List[int], k: int) -> int: s = sum(cardPoints[:k]) ans = s for i inrange(1, k+1): s += cardPoints[-i] - cardPoints[k-i] ans = max(ans, s) return ans
1610. Maximum Number of Visible Points
Helper function that find the angle given location and point. First, include points that are the same as location. Then create a list that contains all angles and sort it, then extend it by adding 360 for each element. Sliding window: as long as the right pointer is valid and the angle difference between left and right is valid, move right pointer. Then update the max points. Next, as long as the left pointer is smaller than the right, and the angle difference between left and right is not valid, move left pointer to the right.
defvisiblePoints(self, points: List[List[int]], angle: int, location: List[int]) -> int: defangle_from_location(point): dx = point[0] - location[0] dy = point[1] - location[1] alpha = math.atan2(dy, dx) * (180 / math.pi) # artan if alpha >= 0: return alpha return alpha + 360 # remove pts that are same as location n = len(points) pts = [p for p in points if p != location] ifnot pts: return n same_loc = n - len(pts) # Sort the angles in ascending order, and extend by adding 360 angles = sorted((angle_from_location(pt) for pt in pts)) angles += [a+360for a in angles] max_pts = 0 i,j = 0,0 while j < len(angles): while j < len(angles) and angles[j] - angles[i] <= angle: j += 1 max_pts = max(max_pts, j-i) while j < len(angles) and i < j and angles[j] - angles[i] > angle: i += 1 return same_loc + max_pts
125. Valid Palindrome
two pointers.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defisPalindrome(self, s: str) -> bool: i, j = 0, len(s) - 1 while i < j: while i < j andnot s[i].isalnum(): i += 1 while i < j andnot s[j].isalnum(): j -= 1 if s[i].lower() != s[j].lower(): returnFalse
i += 1 j -= 1
returnTrue
Copy updated string to another variable and reverse.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6
defisPalindrome(self, s: str) -> bool: string = "" for c in s: if c.isalnum(): string += c.lower() return string == string[::-1]
202. Happy Number
Floyd’s Cycle-Finding Algorithm
Regardless of where the tortoise and hare start in the cycle, they are guaranteed to eventually meet. This is because the hare moves one node closer to the tortoise (in their direction of movement) each step.
Instead of keeping track of just one value in the chain, we keep track of 2, called the slow runner and the fast runner. At each step of the algorithm, the slow runner goes forward by 1 number in the chain, and the fast runner goes forward by 2 numbers (nested calls to the getNext(n) function).
Helper function that find the next number, keep calling this until the number is in the set or it equals 1.
Run time: $O(\log n)$, space: $O(\log n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defisHappy(self, n: int) -> bool: if n == 1: returnTrue s = set() while n notin s: s.add(n) temp_sum = 0 while n > 0: temp_sum += (n % 10)**2 n //= 10 if temp_sum == 1: returnTrue n = temp_sum returnFalse
31. Next Permutation
> First find the point where the descending sub-array starts, and then find the last ascending position, swap them, and swap the elements from the start descending position to the end.
defnextPermutation(self, nums): """ :type nums: List[int] :rtype: None Do not return anything, modify nums in-place instead. """ i = len(nums)-1 while i > 0and nums[i-1] >= nums[i]: i -= 1 if i == 0: nums.reverse() return # find the last ascending position i, j = i-1, len(nums)-1 while nums[j] <= nums[i]: j -= 1
# reverse nums[i], nums[j] = nums[j], nums[i] l = i+1 r = len(nums)-1 while l < r: nums[l], nums[r] = nums[r], nums[l] l, r = l+1, r-1
680. Valid Palindrome II
> Use 2 pointers to walk through the string, if not match, then check if removing either start or end character might be palindrome. Then update the pointers.
words = {word: i for i, word inenumerate(words)} valid_pals = [] for word, k in words.items(): n = len(word) for j inrange(n+1): pref = word[:j] suf = word[j:] if is_palindrome(pref): back = suf[::-1] if back != word and back in words: valid_pals.append([words[back], k]) if j != n and is_palindrome(suf): back = pref[::-1] if back != word and back in words: valid_pals.append([k, words[back]]) return valid_pals
classWordFilter: def__init__(self, words): self.magic = [] for word inreversed(words): self.magic.append(word+'='+word) self.magic = ' '.join(self.magic) deff(self, prefix, suffix): return self.magic.count('=', self.magic.find(suffix+'='+prefix))-1
Store all possible start and end combinations to the dictionary. Use # to separate the prefix and suffix.
Run time: $O(n^2)$, space: $O(n^2)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classWordFilter:
def__init__(self, words: List[str]): self.dic = {} for weight, word inenumerate(words): for i inrange(len(word)+1): for j inrange(len(word)+1): self.dic[word[:i]+"#"+word[j:]] = weight
# Your WordFilter object will be instantiated and called as such: # obj = WordFilter(words) # param_1 = obj.f(prefix,suffix)
659. Split Array into Consecutive Subsequences
Use one dictionary to count each number’s occurrences, and use one dictionary to count the number of valid subsequence ending at certain number. For each number, first decrement its occurrence if any, then check if there exists a valid sequence ending at n-1 already, if so, just decrement that sequence’s occurrence and put the new number in. Otherwise, check if there are two more numbers that could form a new subsequence, if so, decrement the other two numbers’ occurrences and add the ending number of the dictionary. Other than these two cases, we have to return False.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defisPossible(self, nums: List[int]) -> bool: left = collections.Counter(nums) end = collections.Counter() for n in nums: if left[n] == 0: continue left[n] -= 1 if end[n-1] > 0: # there is already a valid subsequence end[n-1] -= 1 end[n] += 1 elif left[n+1] and left[n+2]: # it could form a valid subsequence with unvisited elements left[n+1] -= 1 left[n+2] -= 1 end[n+2] += 1 else: returnFalse returnTrue
299. Bulls and Cows
Use two dictionary to keep track of unmatched chars. After one loop, check for each key in original string, get the min of two occurrences.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defgetHint(self, secret: str, guess: str) -> str: s,g = defaultdict(int), defaultdict(int) match,occurred = 0,0 for i inrange(len(guess)): if secret[i] == guess[i]: match += 1 else: s[secret[i]] += 1 g[guess[i]] += 1 for k in s.keys(): if k in g: occurred += min(s[k], g[k]) returnf"{match}A{occurred}B"
defmaximum(self) -> int: currPrice, timestamp = heapq.heappop(self.maxHeap) #If the price from the heap doesn't match the price the timestamp indicates, keep popping from the heap while -currPrice != self.timestamps[timestamp]: currPrice, timestamp = heapq.heappop(self.maxHeap) heapq.heappush(self.maxHeap, (currPrice, timestamp)) return -currPrice
defminimum(self) -> int: currPrice, timestamp = heapq.heappop(self.minHeap) #If the price from the heap doesn't match the price the timestamp indicates, keep popping from the heap while currPrice != self.timestamps[timestamp]: currPrice, timestamp = heapq.heappop(self.minHeap) heapq.heappush(self.minHeap, (currPrice, timestamp)) return currPrice
# Your StockPrice object will be instantiated and called as such: # obj = StockPrice() # obj.update(timestamp,price) # param_2 = obj.current() # param_3 = obj.maximum() # param_4 = obj.minimum()
981. Time Based Key-Value Store
Maintain two dictionary that store <key, timestamp> and <key, value>. Get the index of the asked timestamp and then use it to retrieve value.
# Your DetectSquares object will be instantiated and called as such: # obj = DetectSquares() # obj.add(point) # param_2 = obj.count(point)
954. Array of Doubled Pairs
Count the frequencies for each element, then sort it by absolute value. If the occurrence of current number is greater than its double, return False, otherwise subtract the occurrence of current number from its double.
Run time: $O(n\log n)$, space: $O(n)$
1 2 3 4 5 6 7 8
defcanReorderDoubled(self, arr: List[int]) -> bool: count = Counter(arr) for x insorted(count, key=abs): if count[x] > count[2*x]: returnFalse count[2*x] -= count[x] returnTrue
defgetOrder(self, tasks: List[List[int]]) -> List[int]: tasks = sorted([t + [i] for i, t inenumerate(tasks)]) heap, ans = [], [] last_enq = 0 #Process Tasks for time, pro, idx in tasks: while heap and last_enq < time: p, i, e = heapq.heappop(heap) last_enq = max(e, last_enq) + p ans.append(i) heapq.heappush(heap, (pro, idx, time)) #Empty heap while heap: _, i, _ = heapq.heappop(heap) ans.append(i) return ans
690. Employee Importance
Put <id, Employee> in a map, then call helper function to add the importance and call recursively.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
""" # Definition for Employee. class Employee: def __init__(self, id: int, importance: int, subordinates: List[int]): self.id = id self.importance = importance self.subordinates = subordinates """
classSolution: defgetImportance(self, employees: List['Employee'], id: int) -> int: defdfs(emp): imp = emps[emp].importance for s in emps[emp].subordinates: imp += dfs(s) return imp emps= {emp.id: emp for emp in employees} return dfs(id)
359. Logger Rate Limiter
Use a dictionary to store <message, most recent logged time>.
Run time: $O(1)$, space: $O(m)$, where $m$ is the number of unique messages.
defset(self, index: int, val: int) -> None: self.cache[index][self.s] = val
defsnap(self) -> int: self.s += 1 return self.s-1
defget(self, index: int, snap_id: int) -> int: if index notin self.cache: return0 else: snaps = self.cache[index] if snap_id in snaps: return snaps[snap_id] keys = list(snaps.keys()) if snap_id > keys[-1]: return snaps[keys[-1]]
ind = bisect.bisect(keys, snap_id) if ind == 0: return0 return snaps[keys[ind-1]]
# Your SnapshotArray object will be instantiated and called as such: # obj = SnapshotArray(length) # obj.set(index,val) # param_2 = obj.snap() # param_3 = obj.get(index,snap_id)
2007. Find Original Array From Doubled Array
Put everything to Counter, and loop through each key. Handle the case when key is zero, and all other cases, first find the smallest number if its half is also in the counter, then append the occurrences to the output, and remove this key and reduce the doubled number occurrences.
deffindOriginalArray(self, changed: List[int]) -> List[int]: counter = collections.Counter(changed) res = [] for k in counter.keys(): if k == 0: # handle zero as special case if counter[k] % 2 != 0: # only one zero return [] res += [0] * (counter[k] // 2) elif counter[k] > 0: x = k # walk down the chain while x % 2 == 0and x // 2in counter: x = x // 2 # walk up and process all numbers within the chain. mark the counts as 0 while x in counter: if counter[x] > 0: if counter[x+x] < counter[x]: return [] res += [x] * counter[x] counter[x+x] -= counter[x] counter[x] = 0 x += x return res
295. Find Median from Data Stream
The idea is to keep two heaps: one for the top half of our data and another is for down half of our data.
If we have even size 2n, then we will keep two heaps with size n
If we have odd size 2n+1, then we will keep size of the small heap n+1 ans the size of large heap n.
When we have new element num, we always put it to small heap, and then normalize our heaps: remove biggest element from the small heap and put it to the large heap. After this operation we can be sure that we have the property that the largest element in small heap is smaller than smaller elements in large heap.
However after this step if we had n, n elements, we will have n, n+1 elements, so we need to put one element from large heap to small heap. https://leetcode.com/problems/find-median-from-data-stream/discuss/1330808/Python-2-heaps-solution-explained
d={} #loop through matrix for i inrange(len(matrix)): for j inrange(len(matrix[i])): #if no entry in dictionary for sum of indices aka the diagonal, create one if i + j notin d: d[i+j] = [matrix[i][j]] else: #If you've already passed over this diagonal, keep adding elements to it! d[i+j].append(matrix[i][j]) # we're done with the pass, let's build our answer array ans= [] #look at the diagonal and each diagonal's elements for entry in d.items(): #each entry looks like (diagonal level (sum of indices), [elem1, elem2, elem3, ...]) #snake time, look at the diagonal level if entry[0] % 2 == 0: #Here we append in reverse order because its an even numbered level/diagonal. [ans.append(x) for x in entry[1][::-1]] else: [ans.append(x) for x in entry[1]] return ans
523. Continuous Subarray Sum
Same as Subarray sum equals K with modification. Basic idea is that, If you get the same remainder again, it means that you’ve encountered some sum which is a multiple of K.
why {0:-1}? In the case the sum of the array a multiple of k, we should store {0:-1} so a modulus of two is noted as already existing.
Run time: $O(n)$, space: $O(k)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defcheckSubarraySum(self, nums: List[int], k: int) -> bool: prefix_sum = 0 # Dictionary of prefix sums and their indices(0 is seen before the array) prefix_sum_indices = {0: -1} for index, num inenumerate(nums): # Compute the remainder when dividing the new sum by k prefix_sum = (prefix_sum + num) % k # Check if this remainder has been encountered before and of length 2 if prefix_sum in prefix_sum_indices and index - prefix_sum_indices[prefix_sum] > 1: returnTrue # Add this index if it is hasn't been encountered before if prefix_sum notin prefix_sum_indices: prefix_sum_indices[prefix_sum] = index returnFalse
249. Group Shifted Strings
Put the difference between each character as key, and the original string as value in the dictionary.
Run time: $O(nk)$, space: $O(nk)$, where $n$ is the length of input strings array, $k$ is the max length of the string in the array.
1 2 3 4 5 6 7 8 9 10 11 12 13
defgroupStrings(self, strings: List[str]) -> List[List[str]]: d = defaultdict(list) for s in strings: key = [] for i inrange(len(s) - 1): # Throw in 26 so that we can normalzie below difference = 26 + ord(s[i+1]) - ord(s[i]) # Wrap around # z + 1 = a key.append(str(difference % 26)) d[','.join(key)].append(s) return d.values()
380. Insert Delete GetRandom O(1)
> Use a dictionary and a list to keep track of the inserted elements. Use dictionary to check whether the element exists, and when we need to remove element, we find the index and put the last element in the original position of the list, and update the dictionary.
Delete: Retrieve an index of element to delete from the hashmap. Move the last element to the place of the element to delete, \mathcal{O}(1)O(1) time. Pop the last element out, \mathcal{O}(1)O(1) time.
242. Valid Anagram
> Use one or two dictionary to check whether all values are zero in the end, or compare if they are the same.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defisAnagram1(self, s, t): dic1, dic2 = {}, {} for item in s: dic1[item] = dic1.get(item, 0) + 1 for item in t: dic2[item] = dic2.get(item, 0) + 1 return dic1 == dic2 defisAnagram2(self, s, t): dic1, dic2 = [0]*26, [0]*26 for item in s: dic1[ord(item)-ord('a')] += 1 for item in t: dic2[ord(item)-ord('a')] += 1 return dic1 == dic2 defisAnagram3(self, s, t): returnsorted(s) == sorted(t)
287. Find the Duplicate Number
Floyd’s Tortoise and Hare (Cycle Detection)
hare = nums[nums[hare]] is twice as fast as tortoise = nums[tortoise]. Since the hare goes fast, it would be the first to enter the cycle and run around the cycle. At some point, the tortoise enters the cycle as well, and since it’s moving slower the hare catches up to the tortoise at some intersection point. Now phase 1 is over, and the tortoise has lost.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
deffindDuplicate(self, nums): # Find the intersection point of the two runners. tortoise = hare = nums[0] whileTrue: tortoise = nums[tortoise] hare = nums[nums[hare]] if tortoise == hare: break # Find the "entrance" to the cycle. tortoise = nums[0] while tortoise != hare: tortoise = nums[tortoise] hare = nums[hare] return hare
Binary search. If the count of the numbers that are smaller than or equal to the mid, update right pointer.
In the binary search approach, instead of doing a linear scan from 1 to n, we can apply a binary search with a goal of finding the smallest number that satisfies the aforementioned property. We start with a search space of [1,n] that has a midpoint midmid. If midmid satisfies the property, we narrow our search space to the left half [1, mid - 1] and continue searching, otherwise, we narrow our search space to the right half [mid + 1, n].
Run time: $O(n\log n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9
deffindDuplicate(self, nums: List[int]) -> int: left, right = 1, len(nums)-1 while left < right: mid = (right + left)//2 ifsum(i <= mid for i in nums) > mid: right = mid else: left = mid+1 return right
Put every element in a set, if it already exists, return it.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6
deffindDuplicate(self, nums: List[int]) -> int: seen = set() for num in nums: if num in seen: return num seen.add(num)
217. Contains Duplicate
Build a set using input array. If every element is distinct, then their length should be equal.
Negate the number that is in this index. In the end, if the number is still positive, put the corresponding index+1 to the output array.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11
deffindDisappearedNumbers(self, nums: List[int]) -> List[int]: ans = [] for i inrange(len(nums)): temp = abs(nums[i])-1 if nums[temp] > 0: nums[temp] *= -1
for i inrange(len(nums)): if nums[i] > 0: ans.append(i+1) return ans
Hashset.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8
deffindDisappearedNumbers(self, nums: List[int]) -> List[int]: s = set(nums) n = len(nums) ans = [] for i inrange(1, n+1): if i notin s: ans.append(i) return ans
def__init__(self, capacity): """ :type capacity: int """ self.capacity = capacity # cache will contain key:[value, frequency] # default dict will contain all the keys having the same frequency of occurrence. # {frequency:[key1, key2, key3]} self.cache = {} self.freqCache = collections.defaultdict(list)
defget(self, key): """ :type key: int :rtype: int """ val = self.cache.get(key, -1) if val == -1: return val value,freq = val # if key is found, increase the frequency in frequency cache and data cache self.freqCache[freq].remove(key) ifnot self.freqCache[freq]: del self.freqCache[freq] self.freqCache[freq+1].append(key)
self.cache[key] = [value, freq+1] return value
defput(self, key, value): """ :type key: int :type value: int :rtype: None """ if self.capacity <=0: return val = self.cache.get(key, -1) initfreq=1 if val == -1: iflen(self.cache) == self.capacity: #print self.freqCache.items() minFreq, delList = min(self.freqCache.items(), key=lambda x:x[0]) #print delList delkey = delList[0] del self.cache[delkey] self.freqCache[minFreq].remove(delkey) #insert in cache self.cache.update({key:[value,initfreq]}) self.freqCache[initfreq].append(key) else: self.cache.update({key:[value,initfreq]}) self.freqCache[initfreq].append(key) else: oldval,freq = val self.freqCache[freq].remove(key) ifnot self.freqCache[freq]: del self.freqCache[freq] self.freqCache[freq+1].append(key) self.cache[key] = [value,freq+1]
# Your LFUCache object will be instantiated and called as such: # obj = LFUCache(capacity) # param_1 = obj.get(key) # obj.put(key,value)
146. LRU Cache
> Use OrderedDict. pop key: d.pop(key). Remove least used key: d.popitem(last=False).
defget(self, key: int) -> int: if key in self.dic: n = self.dic[key] self._remove(n) self._add(n) return n.value return -1
defput(self, key: int, value: int) -> None: if key in self.dic: self._remove(self.dic[key]) n = Node(key, value) self._add(n) self.dic[key] = n iflen(self.dic) > self.cap: n = self.next self._remove(n) del self.dic[n.key] def_remove(self, node): p = node.prev n = node.next p.next = n n.prev = p
def_add(self, node): p = self.prev p.next = node self.prev = node node.prev = p node.next = self
defnumIslands(self, grid: List[List[str]]) -> int: m, n = len(grid), len(grid[0]) dirs = [0, 1, 0, -1, 0] defdfs(r, c): ifnot (0 <= r < m and0 <= c < n) or grid[r][c] == "0": return0 grid[r][c] = "0"# Mark as visited for i inrange(4): dfs(r + dirs[i], c + dirs[i+1]) return1 ans = 0 for r inrange(m): for c inrange(n): ans += dfs(r, c) return ans
defnumIslands(self, grid: List[List[str]]) -> int: deffind(x: int) -> int: if parent[x] != x: parent[x] = find(parent[x]) return parent[x] defunion(x: int, y: int) -> None: xroot = find(x) yroot = find(y) if xroot == yroot: return if rank[xroot] < rank[yroot]: xroot, yroot = yroot, xroot parent[yroot] = xroot rank[xroot] = max(rank[xroot], rank[yroot]+1) self.count -= 1 iflen(grid) == 0: return0 m, n = len(grid), len(grid[0]) self.count = sum(grid[i][j]=='1'for i inrange(m) for j inrange(n)) parent = [i for i inrange(m*n)] rank = [0] * m * n
for i inrange(m): for j inrange(n): if grid[i][j] == '0': continue index = i*n + j if j < n-1and grid[i][j+1] == '1': union(index, index+1) if i < m-1and grid[i+1][j] == '1': union(index, index+n) return self.count
defnumIslands(self, grid: List[List[str]]) -> int: DIR = [0, 1, 0, -1, 0] m, n = len(grid), len(grid[0]) uf = UnionFind(m*n) component = 0 for r inrange(m): for c inrange(n): if grid[r][c] == "0": continue component += 1 curId = r * n + c for i inrange(4): nr, nc = r + DIR[i], c + DIR[i+1] ifnot (0 <= nr < m and0 <= nc < n) or grid[nr][nc] == "0": continue neiId = nr * n + nc if uf.union(curId, neiId): component -= 1 return component
classUnionFind: def__init__(self, n): self.parent = [i for i inrange(n)] self.size = [1] * n deffind(self, u): if u != self.parent[u]: self.parent[u] = self.find(self.parent[u]) # Path compression return self.parent[u] defunion(self, u, v): pu, pv = self.find(u), self.find(v) if pu == pv: returnFalse if self.size[pu] < self.size[pv]: # Merge pu to pv self.size[pv] += self.size[pu] self.parent[pu] = pv else: self.size[pu] += self.size[pv] self.parent[pv] = pu returnTrue
defnumIslands(self, grid: List[List[str]]) -> int: m, n = len(grid), len(grid[0]) DIR = [0, 1, 0, -1, 0]
defbfs(r, c): if grid[r][c] == "0": return0 q = deque([(r, c)]) while q: r, c = q.popleft() for i inrange(4): nr, nc = r + DIR[i], c + DIR[i+1] if nr < 0or nr == m or nc < 0or nc == n or grid[nr][nc] == "0": continue grid[nr][nc] = "0"# Mark as visited q.append([nr, nc]) return1
ans = 0 for r inrange(m): for c inrange(n): ans += bfs(r, c) return ans
721. Accounts Merge
Use one map that stores email to id, one map for id to email, one map for email to name.
First for each user, gets their name, then loop through each email, and add <email, name> to the last dict. If this email is not in the email-to-id dict, add <email, id> to it, and put <id, email> to the id-to-email dict. Remember to increment the id for the next user. Then, union the first email with every email including itself.
Next, create a dictionary that stores <id, set of emails>. For each id, find its root, create an entry for its root and add the emails retrieved using current id from id-to-email dictionary.
For the final output, look at all <id, email set> pair, get the name from the email-to-name map using the id-to-email map. Then, get the name, and concatenate with the sorted list of emails.
Run time: $O(SlogS)$, space: $O(S)$, where S is sum of all emails of accounts.
classSolution: defaccountsMerge(self, accounts: List[List[str]]) -> List[List[str]]: uf = UnionFind(10000) map_email_id = {} map_id_email = {} map_email_name = {} id = 0 for acc in accounts: name = acc[0] for email in acc[1:]: map_email_name[email] = name if email notin map_email_id: map_email_id[email] = id map_id_email[id] = email id += 1 uf.union(map_email_id[acc[1]], map_email_id[email]) # union 1st email and ith emails -> union emails in same account together merged_emails = defaultdict(set) for _idinrange(id): merged_emails[uf.find(_id)].add(map_id_email[_id]) ans = [] for (_id, emails) in merged_emails.items(): name = map_email_name[map_id_email[_id]] ans.append([name] + list(sorted(emails))) return ans
classUnionFind: def__init__(self, n): self.parent = [i for i inrange(n)] self.size = [1] * n deffind(self, x): if x != self.parent[x]: self.parent[x] = self.find(self.parent[x]) # Path compression return self.parent[x] defunion(self, u, v): pu, pv = self.find(u), self.find(v) if pu == pv: returnFalse# Return False if u and v are already union if self.size[pu] > self.size[pv]: # Union by larger size self.size[pu] += self.size[pv] self.parent[pv] = pu else: self.size[pv] += self.size[pu] self.parent[pu] = pv returnTrue
803. Bricks Falling When Hit
First initialize the rank and parent to store (-1,-1). The num_child dictionary stores the (x,y) position and the number of children connected to it. The set hits stores each hit to have easier access. First we initialize all 1 positions as nodes, if it is on the first row (connected to the ceiling), then union it with (-1,-1). Otherwise, connect it to its four neighbors if any.
Once this step is complete, we go through each hit in reversed order, if the hit position is empty, append 0 and continue. If the area is 1, we first get the children of (-1,-1), i.e., the number of bricks connected to ceiling. Then we initialize the node in the hit position. If the row is 0, we connect the node with (-1,-1), and connect its four neighbors. Now, we get the number of children of position (-1,-1), and the curr-prev-1 would be the number of falling bricks, as if the hit brick is added, we may be able to connect more bricks to the ceiling, and they would fall when this brick is hit. https://leetcode.com/problems/bricks-falling-when-hit/discuss/1695192/Python-UF-Template
classSolution: defhitBricks(self, grid: List[List[int]], hits: List[List[int]]) -> List[int]: ifnot grid: return [] defunion(node1, node2): root1 = find(node1) root2 = find(node2) if root1 != root2: if rank[root1] > rank[root2]: parent[root2] = root1 num_child[root1] += num_child[root2] elif rank[root2] > rank[root1]: parent[root1] = root2 num_child[root2] += num_child[root1] else: # rank[root1] == rank[root2] parent[root2] = root1 num_child[root1] += num_child[root2] rank[root1] += 1 deffind(node): if parent[node] != node: parent[node] = find(parent[node]) return parent[node] definit_node(node): parent[node] = node num_child[node] = 1 rank[node] = 1 defconnect_neighbors(node): for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]: x,y = node[0]+dx, node[1]+dy if (x,y) in parent: union(node, (x,y)) m,n = len(grid), len(grid[0]) # add virtual parent (-1, -1) for all of first row points rank = {(-1, -1): m * n} parent = {(-1, -1): (-1, -1)} num_child = collections.defaultdict(int) hits_cache = {(x,y) for [x,y] in hits}
for row inrange(m): for col inrange(n): if (row, col) in hits_cache: continue if grid[row][col] == 1: init_node((row, col)) if row == 0: union((-1,-1), (row, col)) else: connect_neighbors((row, col)) res = [] for row, col in hits[::-1]: if grid[row][col] != 1: res.append(0) continue pre = num_child[(-1,-1)] init_node((row, col)) if row == 0: union((-1,-1), (row, col)) connect_neighbors((row, col)) cur = num_child[(-1,-1)] res.append(max(0, cur-pre-1)) return res[::-1]
827. Making A Large Island
Union Find. First initialize the dictionary for parents and size. Then For each 1 island, explore its down and right neighbors that are 1 and try to union them. For each cell, explore its all four neighbors and if it is 0, find its parent and add to a set. In the end, get the max size for all parents. If there is no zero in the matrix, return max size.
deflargestIsland(self, grid: List[List[int]]) -> int: N = len(grid) # union find and size of components uf = { (r, c): (r, c) for r inrange(N) for c inrange(N) if grid[r][c] } size = { (r, c): 1 for r inrange(N) for c inrange(N) if grid[r][c] } deffind(x): if x != uf[x]: uf[x] = find(uf[x]) return uf[x] defunion(x, y): rootx = find(x) rooty = find(y) if rootx != rooty: uf[rootx] = rooty size[rooty] += size[rootx] # union adjacant 1s for r inrange(N): for c inrange(N): if grid[r][c]: for nr, nc in [[r+1, c], [r, c+1]]: if0 <= nr < N and0 <= nc < N and grid[nr][nc]: union((r,c), (nr,nc)) # iterate all 0s to find the max area max_area = 0 has_zero = False for r inrange(N): for c inrange(N): if grid[r][c] == 0: has_zero = True adj_islands = set() for nr, nc in [[r+1, c], [r, c+1], [r-1, c], [r, c-1]]: if0 <= nr < N and0 <= nc < N and grid[nr][nc]: adj_islands.add(find((nr, nc))) area = 1 + sum(size[adj_island] for adj_island in adj_islands) max_area = max(max_area, area) return max_area if has_zero else N * N
deflargestIsland(self, grid: List[List[int]]) -> int: DIR = [0, 1, 0, -1, 0] m, n, nextColor = len(grid), len(grid[0]), 2 componentSize = defaultdict(int)
defpaint(r, c, color): if r < 0or r == m or c < 0or c == n or grid[r][c] != 1: return grid[r][c] = color componentSize[color] += 1 for i inrange(4): paint(r + DIR[i], c + DIR[i + 1], color)
for r inrange(m): for c inrange(n): if grid[r][c] != 1: continue# Only paint when it's an island cell paint(r, c, nextColor) nextColor += 1
ans = max(componentSize.values() or [0]) for r inrange(m): for c inrange(n): if grid[r][c] != 0: continue neiColors = set() for i inrange(4): nr, nc = r + DIR[i], c + DIR[i + 1] if nr < 0or nr == m or nc < 0or nc == n or grid[nr][nc] == 0: continue neiColors.add(grid[nr][nc]) sizeFormed = 1# Start with 1, which is matrix[r][c] when turning from 0 into 1 for color in neiColors: sizeFormed += componentSize[color] ans = max(ans, sizeFormed)
return ans
DP
>
Run time: $O()$, space: $O()$
1
1235. Maximum Profit in Job Scheduling
> If we don't do this job, nothing will be changed.
If we do this job, binary search in the dp to find the largest profit we can make before start time s.
So we also know the maximum current profit that we can make doing this job. Compare with last element in the dp, we make more money, it worth doing this job, then we add the pair of [e, cur] to the back of dp. Otherwise, we'd like not to do this job.
Run time: $O(n\log n)$, space: $O(n)$
1 2 3 4 5 6 7 8
defjobScheduling(self, startTime, endTime, profit): jobs = sorted(zip(startTime, endTime, profit), key=lambda v: v[1]) dp = [[0, 0]] for s, e, p in jobs: i = bisect.bisect(dp, [s + 1]) - 1 if dp[i][1] + p > dp[-1][1]: dp.append([e, dp[i][1] + p]) return dp[-1][1]
122. Best Time to Buy and Sell Stock II
Since we can buy/sell anytime, we can use a greedy approach.
Just track the sum of profit, add to it whenever the previous day’s price is lower than the current.
defisMatch(self, s: str, p: str) -> bool: # The DP table and the string s and p use the same indexes i and j, but # table[i][j] means the match status between p[:i] and s[:j], i.e. # table[0][0] means the match status of two empty strings, and # table[1][1] means the match status of p[0] and s[0]. Therefore, when # refering to the i-th and the j-th characters of p and s for updating # table[i][j], we use p[i - 1] and s[j - 1].
# Initialize the table with False. The first row is satisfied. table = [[False] * (len(s) + 1) for _ inrange(len(p) + 1)]
# Update the corner case of matching two empty strings. table[0][0] = True
# Update the corner case of when s is an empty string but p is not. # Since each '*' can eliminate the charter before it, the table is # vertically updated by the one before previous. [test_symbol_0] for i inrange(2, len(p) + 1): table[i][0] = table[i - 2][0] and p[i - 1] == '*'
for i inrange(1, len(p) + 1): for j inrange(1, len(s) + 1): if p[i - 1] != "*": # Update the table by referring the diagonal element. table[i][j] = table[i - 1][j - 1] and \ (p[i - 1] == s[j - 1] or p[i - 1] == '.') else: # Eliminations (referring to the vertical element) # Either refer to the one before previous or the previous. # I.e. * eliminate the previous or count the previous. # [test_symbol_1] table[i][j] = table[i - 2][j] or table[i - 1][j]
# Propagations (referring to the horizontal element) # If p's previous one is equal to the current s, with # helps of *, the status can be propagated from the left. # [test_symbol_2] if p[i - 2] == s[j - 1] or p[i - 2] == '.': table[i][j] |= table[i][j - 1]
return table[-1][-1]
322. Coin Change
Assume dp[i] is the fewest number of coins making up amount i, then for every coin in coins, dp[i] = min(dp[i - coin] + 1).
Run time: $O(mn)$, space: $O(m)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defcoinChange(self, coins: List[int], amount: int) -> int: dp = [float('inf')] * (amount+1) dp[0] = 0 coins.sort() for i inrange(1,amount+1): temp = [float('inf')] for c in coins: if i - c < 0: break temp.append(dp[i-c]) dp[i] = min(temp)+1
return dp[amount] if dp[amount] != float('inf') else -1
403. Frog Jump
Use a dictionary dic which maps the position of a stone in stones to the set of stepsizes that can jump onto the stone. We initialize dic = {0:{0}}, meaning that we start with the stone at position 0. Next, we iterate i over range(len(stones)), and check if stones[i] is in dic, if it is, it means that there are previous jumps that land on this stone, and we can continue jumping ahead, in which case we iterate over all val in dic[stones[i]], and for each val, we can continue jumping ahead with three stepsizes (val-1, val, and val+1). Therefore, we add val-1 to dic[stones[i]+val-1], val to dic[stones[i]+val], and val+1 to dic[stones[i]+val+1]. Finally, we check if stones[-1] is in dic, if it is, we return True; Else we return False.
Run time: $O(n^2)$, space: $O(n^2)$
1 2 3 4 5 6 7 8 9 10 11 12
defcanCross(self, stones: List[int]) -> bool: dic = collections.defaultdict(set) # <steps, possible next steps> dic[0].add(0) for i inrange(len(stones)): if stones[i] in dic: for val in dic[stones[i]]: if val > 0: dic[stones[i]+val].add(val) if val > 1: dic[stones[i]+val-1].add(val-1) dic[stones[i]+val+1].add(val+1) return stones[-1] in dic
1406. Stone Game III
dp[i] means, if we ignore before A[i],
what’s the highest score that Alex can win over the Bob?
There are three option for Alice to choose.
Take A[i], win take - dp[i+1]
Take A[i] + A[i+1], win take - dp[i+2]
Take A[i] + A[i+1] + A[i+2], win take - dp[i+3]
dp[i] equals the best outcome of these three solutions. https://leetcode.com/problems/stone-game-iii/discuss/564260/JavaC%2B%2BPython-DP-O(1)-Space
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defstoneGameIII(self, stoneValue: List[int]) -> str: dp = [0] * 4 for i inrange(len(stoneValue)-1, -1, -1): dp[i % 4] = -float('inf') take = 0 for k inrange(3): if i+k < len(stoneValue): take += stoneValue[i+k] dp[i % 4] = max(dp[i % 4], take - dp[(i+k+1) % 4]) if dp[0] > 0: return"Alice" if dp[0] < 0: return"Bob" return"Tie"
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defstoneGameIII(self, stoneValue: List[int]) -> str: n = len(stoneValue) dp = [0] * (n+1) for i inrange(n-1, -1, -1): k = 0 take = 0 dp[i] = -float('inf') while k < 3and i + k < n: take += stoneValue[i+k] dp[i] = max(dp[i], take - dp[i+k+1]) k += 1 if dp[0] > 0: return"Alice" if dp[0] < 0: return"Bob" return"Tie"
deflongestLine(self, mat: List[List[int]]) -> int: ifnot mat ornot mat[0]: return0 m,n = len(mat), len(mat[0]) dp = [[[0]*4for __ inrange(n)] for _ inrange(m)] max_len = 0 for row inrange(m): for col inrange(n): if mat[row][col] == 1: if col-1 >= 0: dp[row][col][0] = dp[row][col-1][0]+1 else: dp[row][col][0] = 1 if row-1 >= 0: dp[row][col][1] = dp[row-1][col][1]+1 else: dp[row][col][1] = 1 if row-1 >= 0and col-1 >= 0: dp[row][col][2] = dp[row-1][col-1][2]+1 else: dp[row][col][2] = 1 if row-1 >= 0and col+1 < n: dp[row][col][3] = dp[row-1][col+1][3]+1 else: dp[row][col][3] = 1 max_len = max(max_len, dp[row][col][0], dp[row][col][1], dp[row][col][2], dp[row][col][3]) return max_len
1691. Maximum Height by Stacking Cuboids
Sort items by its three side lengths, then sort all items in descending order. Then keep one array of longest side for each box. For each box, we look at all possible larger boxes and see if its all three sides are smaller, then update the max height to be the longest side plus the max height at index of the larger box. In the end return the max of max height array.
Run time: $O(n^2)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defmaxHeight(self, cuboids: List[List[int]]) -> int: Info = [] for cube in cuboids: Info.append(sorted(cube))
Info.sort(reverse = True) max_height = [Info[i][2] for i inrange(len(Info))]
# add boxes from bottom to top (i sits on top of j) for i inrange(len(Info)): for j inrange(i): if Info[i][0] <= Info[j][0] and \ Info[i][1] <= Info[j][1] and \ Info[i][2] <= Info[j][2]: max_height[i] = max(max_height[i], max_height[j] + Info[i][2]) # assume height is the last dimension
defnumberOfUniqueGoodSubsequences(self, binary: str) -> int: mod = 10**9 + 7 dp = [0, 0] for c in binary: dp[int(c)] = (sum(dp) + int(c)) % mod if'0'in binary: return (sum(dp)+1) % mod returnsum(dp) % mod
deftrap(self, height: List[int]) -> int: ifnot height: return0 n = len(height) l, r = 0, n-1 max_l = height[l] max_r = height[r] ans = 0 while l < r: if max_l < max_r: ans += max_l - height[l] l += 1 max_l = max(max_l, height[l]) else: ans += max_r - height[r] r -= 1 max_r = max(max_r, height[r]) return ans
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11
deftrap(self, height: List[int]) -> int: waterLevel = [] left = 0 for h in height: left = max(left, h) waterLevel += [left] # over-fill it to left max height right = 0 for i, h inreversed(list(enumerate(height))): right = max(right, h) waterLevel[i] = min(waterLevel[i], right) - h # drain to the right height returnsum(waterLevel)
664. Strange Printer
Let dp(i, j) be the number of turns needed to print S[i:j+1].Then, when trying to complete the printing of interval [i, k] (with S[i] == S[k]), the job will take the same number of turns as painting [i, k-1]. This is because it is always at least as good to print [i, k] first in one turn rather than separately.
Also, we would need to complete [k+1, j]. So in total, our candidate answer is dp(i, k-1) + dp(k+1, j). Of course, when k == i, our candidate is 1 + dp(i+1, j): we paint S[i] in one turn, then paint the rest in dp(i+1, j) turns. https://leetcode.com/problems/strange-printer/discuss/106795/Python-Straightforward-DP-with-Explanation
Run time: $O(n^3)$, space: $O(n^2)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defstrangePrinter(self, s: str) -> int: memo = {} defdp(i, j): if i > j: return0 if (i, j) notin memo: ans = dp(i+1, j) + 1 for k inrange(i+1, j+1): if s[k] == s[i]: ans = min(ans, dp(i, k-1) + dp(k+1, j)) memo[i, j] = ans return memo[i, j]
return dp(0, len(s) - 1)
1218. Longest Arithmetic Subsequence of Given Difference
Use a dictionary to store <last number, length of subsequence>, return 0 if not found, then add 1.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5
deflongestSubsequence(self, arr: List[int], difference: int) -> int: res = {} for a in arr: res[a] = res.get(a-difference, 0) + 1 returnmax(res.values())
defcheckRecord(self, n: int) -> int: """ i=0, possible =1 i=1, possible =2 (P,L) i=2, possible =4 (PP,PL,LP,LL) """ if n==1:return3 if n==0: return0 mod=1000000007 dp=[0for i inrange(n+1)] dp[0],dp[1],dp[2]=1,2,4 for i inrange(3,n+1): dp[i]=(dp[i-1]+dp[i-2]+dp[i-3] )% mod res=dp[n] for i inrange(1,n+1): res+=dp[i-1]*dp[n -i]%mod res=res%mod return res
1277. Count Square Submatrices with All Ones
Get the min of the three neighbors, then add value in the current cell.
Run time: $O(mn)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defcountSquares(self, matrix: List[List[int]]) -> int: ifnot matrix orlen(matrix) == 0: return0 m,n = len(matrix), len(matrix[0]) count = 0 for i inrange(m): for j inrange(n): if matrix[i][j] == 1: if i == 0or j == 0: count += 1 else: temp = min(matrix[i-1][j-1], matrix[i-1][j], matrix[i][j-1]) + matrix[i][j] count += temp matrix[i][j] = temp return count
1937. Maximum Number of Points with Cost
We traverse current_points left to right and store the maximum possible score that the next row can get, taking into account only the elements with indexes [0, col], then do this reversely.
Run time: $O(mn)$, space: $O(m)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defmaxPoints(self, points: List[List[int]]) -> int: current_points = points[0][:] for row inrange(1, len(points)): max_col_points = -float("inf") for col inrange(len(current_points)): max_col_points = max(max_col_points - 1, current_points[col]) current_points[col] = max_col_points max_col_points = -float("inf") for col inrange(len(current_points)-1, -1, -1): max_col_points = max(max_col_points - 1, current_points[col]) current_points[col] = max_col_points for col inrange(len(current_points)): current_points[col] = points[row][col] + current_points[col] returnmax(current_points)
140. Word Break II
For any word (denoted as \text{w}w) in the dictionary, if it matches with a prefix of the input string, we then can divide the string into two parts: the word and the postfix, i.e. s = \text{w} + \text{postfix}s=w+postfix.
Run time: $ \mathcal{O}(N^2 + 2 ^ N + W)$, space: $ \mathcal{O}(2^N \cdot N + N^2 + W)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defwordBreak(self, s: str, wordDict: List[str]) -> List[str]: memo = {} defdfs(s): if s in memo: return memo[s] res = [] for x in wordDict: if s.startswith(x): if s == x: res += [x] else: for y in dfs(s[len(x):]): res += [x + ' ' + y] memo[s] = res return res return dfs(s)
Recursion
Run time: $ \mathcal{O}(N^2 + 2 ^ N + W)$, space: $ \mathcal{O}(2^N \cdot N + N^2 + W)$
1 2 3 4 5 6 7 8 9 10 11 12
defhelper(s, string, ans): if s == "": ans.append(string.strip()) return for i inrange(len(s)+1): if s[:i] in wordDict: temp = string + " "+ s[:i] helper(s[i:], temp, ans)
ans = [] helper(s, "", ans) return ans
53. Maximum Subarray
> One variable to keep track of current subarray, one variable to track max subarray.
defisMatch(self, s: str, p: str) -> bool: m,n = len(s), len(p) dp = [[False]*(n+1) for _ inrange(m+1)] dp[0][0] = True for i inrange(1, n+1): if p[i-1] == '*': dp[0][i] = dp[0][i-2] # same as the char before * for i inrange(1, m+1): for j inrange(1, n+1): if p[j-1] == '.': # skip current one dp[i][j] = dp[i-1][j-1] elif p[j-1] != '*': # normal char must match dp[i][j] = dp[i-1][j-1] and p[j-1] == s[i-1] elif p[j-2] != '.'and p[j-2] != s[i-1]: # ended at * dp[i][j] = dp[i][j-2] else: # all other cases dp[i][j] = dp[i-1][j] or dp[i][j-1] or dp[i][j-2] return dp[-1][-1]
416. Partition Equal Subset Sum
We could further optimize Approach 3. We must understand that for any array element ii, we need results of the previous iteration (i-1) only. Hence, we could achieve the same using a one-dimensional array as well.
Run time: $O(mn)$, space: $O(m)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
defcanPartition(self, nums: List[int]) -> bool: sum_ = sum(nums) if sum_ % 2 != 0: returnFalse half = sum_ // 2 n = len(nums) dp = [False]*(sum_+1) dp[0] = True for i inrange(1, n+1): curr = nums[i-1] for j inrange(sum_, curr-1, -1): dp[j] = dp[j] or dp[j-curr] return dp[sum_]
We maintain a 2D array ,dp[n][subSetSum] For an array element ii and sum jj in array nums,
dp[i][j]=true if the sum jj can be formed by array elements in subset nums[0]…nums[i],otherwise dp[i][j]=false
Run time: $O(mn)$, space: $O(mn)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defcanPartition(self, nums: List[int]) -> bool: sum_ = sum(nums) if sum_ % 2 != 0: returnFalse half = sum_ // 2 n = len(nums) dp = [[False]*(sum_+1) for _ inrange(n+1)] dp[0][0] = True for i inrange(1, n+1): curr = nums[i-1] for j inrange(sum_ + 1): if j < curr: dp[i][j] = dp[i-1][j] else: dp[i][j] = dp[i-1][j] or dp[i-1][j-curr] return dp[n][sum_]
300. Longest Increasing Subsequence
Use finary search bisect_left
Run time: $O(n\log n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
deflengthOfLIS(self, nums: List[int]) -> int: tails = [0] * len(nums) size = 0 for x in nums: lo, hi = 0, size while lo != hi: mid = (lo + hi) // 2 if tails[mid] < x: lo = mid + 1 else: hi = mid tails[lo] = x size = max(lo + 1, size) return size
Double for loop and update max if the inner value is smaller than current value.
Run time: $O(n^2)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10
deflengthOfLIS(self, nums: List[int]) -> int: n = len(nums) dp = [1] * (n+1) max_len = 0 for i inrange(1, n): for j inrange(i): if nums[j] < nums[i]: dp[i] = max(dp[j]+1, dp[i]) max_len = max(dp[i], max_len) return max_len
139. Word Break
First put list into a set for easier access. Initialize an array of size n+1 and set the first entry to be True. Loop from 1 to n, and loop from 0 to i-1, check if dp[j] is True and the substring between j and i is valid. If so, set this entry of the dp array to be true.
for i inrange(1, n+1): for j inrange(i): if dp[j] and s[j:i] in word_set: dp[i] = True break return dp[len(s)]
279. Perfect Squares
> To calculate the value of numSquares(n), first we need to calculate all the values before n.
Run time: $O(n\sqrt{n})$ or $O(\frac{\sqrt{n}^{h+1}-1}{\sqrt{n}-1})=O(n^{\frac{h}{2}})$, space: $O(n)$ or $O((\sqrt{n})^h)$, where h is the height of the N-ary tree.
Again, first of all, we prepare a list of square numbers (named square_nums) that are less than the given number n.
We then create a queue variable which would keep all the remainders to enumerate at each level.
In the main loop, we iterate over the queue variable. At each iteration, we check if the remainder is one of the square numbers. If the remainder is not a square number, we subtract it with one of the square numbers to obtain a new remainder and then add the new remainder to the next_queue for the iteration of the next level. We break out of the loop once we encounter a remainder that is of a square number, which also means that we find the solution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution: defnumSquares(self, n: int) -> int: queue = deque([(n, 0)]) visited = {n} while queue: curr, step = queue.popleft() ifint(curr ** 0.5) == curr ** 0.5: return step + 1 for i inrange(int(curr ** 0.5) + 1): if curr - (i ** 2) notin visited: queue.append((curr - (i ** 2), step + 1)) visited.add(curr - (i ** 2))
1 2 3 4 5 6 7 8 9
defnumSquares(self, n: int) -> int: dp = [i for i inrange(n+1)] for i inrange(3, n+1): j = 1 while j**2 <= i: dp[i] = min(dp[i], dp[i-j**2]+1) j += 1 return dp[-1]
221. Maximal Square
Find squares whose bottom right corner is formed by the current cell. If we can modify the input, the space complexity can be further reduced to $O(1)$.
Run time: $O(mn)$, space: $O(mn)$ or $O(n)$ or $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defmaximalSquare(self, matrix: List[List[str]]) -> int: m, n, ans = len(matrix), len(matrix[0]), 0 for i, j in product(range(m-1, -1, -1), range(n-1, -1, -1)): if i == m-1or j == n-1: matrix[i][j] = int(matrix[i][j]) ans = max(ans, matrix[i][j]) else: if matrix[i][j] == '1': matrix[i][j] = 1 + min(matrix[i+1][j], matrix[i][j+1], matrix[i+1][j+1]) else: matrix[i][j] = 0 ans = max(ans, matrix[i][j]) return ans * ans
1 2 3 4 5 6 7 8 9 10 11
defmaximalSquare(self, matrix: List[List[str]]) -> int: m, n, ans = len(matrix), len(matrix[0]), 0 dp = [[0]*(n+1) for i inrange(m+1)] for i, j in product(range(m-1, -1, -1), range(n-1, -1, -1)): if matrix[i][j] == '1': dp[i][j] = 1 + min(dp[i+1][j], dp[i][j+1], dp[i+1][j+1]) else: dp[i][j] = 0 ans = max(ans, dp[i][j]) return ans * ans
542. 01 Matrix
Two for loops, one goes from top left to bottom right, one goes from bottom right to top left. In this way we could find the minimum distance over all four directions.
Run time: $O(n^2)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defupdateMatrix(self, mat: List[List[int]]) -> List[List[int]]: m, n = len(mat), len(mat[0])
for r inrange(m): for c inrange(n): if mat[r][c] > 0: top = mat[r - 1][c] if r > 0else math.inf left = mat[r][c - 1] if c > 0else math.inf mat[r][c] = min(top, left) + 1
for r inrange(m - 1, -1, -1): for c inrange(n - 1, -1, -1): if mat[r][c] > 0: bottom = mat[r + 1][c] if r < m - 1else math.inf right = mat[r][c + 1] if c < n - 1else math.inf mat[r][c] = min(mat[r][c], bottom + 1, right + 1)
return mat
64. Minimum Path Sum
Instead of using a 2D matrix for dp, we can do the same work using a dp array of the row size, since for making the current entry all we need is the dp entry for the bottom and the right element. Thus, we start by initializing only the last element of the array as the last element of the given matrix. The last entry is the bottom rightmost element of the given matrix.
Run time: $O(n^2)$, space: $O(n^2)$ or $O(n)$ or $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defminPathSum(self, grid: List[List[int]]) -> int: m,n = len(grid), len(grid[0]) dp = [0for _ inrange(n)] for i inrange(m-1, -1, -1): for j inrange(n-1, -1, -1): if i == m-1and j != n-1: dp[j] = grid[i][j] + dp[j+1] elif j == n-1and i != m-1: dp[j] = grid[i][j] + dp[j] elif i != m-1and j != n-1: dp[j] = grid[i][j] + min(dp[j], dp[j+1]) else: dp[j] = grid[i][j] return dp[0]
1 2 3 4 5 6 7 8 9 10 11 12
defminPathSum(self, grid: List[List[int]]) -> int: m = len(grid) n = len(grid[0]) for i inrange(1, n): grid[0][i] += grid[0][i-1] for i inrange(1, m): grid[i][0] += grid[i-1][0] for i inrange(1, m): for j inrange(1, n): grid[i][j] += min(grid[i-1][j], grid[i][j-1]) return grid[-1][-1]
413. Arithmetic Slices
we only require the element dp[i−1] to determine the value to be entered at dp[i]. Thus, instead of making use of a 1-D array to store the required data, we can simply keep a track of just the last element.
Run time: $O(n)$, space: $O(n)$ or $O(1)$
1 2 3 4 5 6 7 8 9 10
defnumberOfArithmeticSlices(self, nums: List[int]) -> int: dp = 0 total = 0 for i inrange(2, len(nums)): if nums[i] - nums[i-1] == nums[i-1] - nums[i-2]: dp += 1 total += dp else: dp = 0 return total
1 2 3 4 5 6 7 8 9
defnumberOfArithmeticSlices(self, nums: List[int]) -> int: iflen(nums) < 3: return0 dp = [0for _ inrange(len(nums))] for i inrange(2, len(nums)): if nums[i] - nums[i-1] == nums[i-1] - nums[i-2]: dp[i] = dp[i-1]+1 returnsum(dp)
198. House Robber
> Either steal this house and the n-2 ones, or not steal this one. We could just use two variables and keep shifting them.
Run time: $O(n)$, space: $O(n)$ or $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
defrob(self, nums: List[int]) -> int: ifnot nums: return0 rob_prev_two = 0 rob_prev_one = nums[0] # DP table calculations. for i inrange(1, len(nums)): current = max(rob_prev_one, rob_prev_two + nums[i]) # Update the variables rob_prev_two = rob_prev_one rob_prev_one = current return rob_prev_one
1 2 3 4 5 6 7 8
defrob(self, nums: List[int]) -> int: iflen(nums) == 1: return nums[0] ans = [nums[0], max(nums[0], nums[1])] for i inrange(2, len(nums)): ans.append(max(nums[i] + ans[i-2], ans[i-1])) return ans[-1]
70. Climbing Stairs
> Since the `i`th stairs can be reached either from the `i-1`th stair or `i-2`th stair, so the number of ways is the sum of ways at `i-1`th stair and `i-2`th stair.
Run time: $O(n)$, space: $O(n)$ or $O(1)$
1 2 3 4 5
defclimbStairs(self, n: int) -> int: a, b = 1, 1 for i inrange(n): a, b = b, a + b return a
1 2 3 4 5 6 7 8 9 10 11
defclimbStairs(self, n: int) -> int: if n <= 0: return0 if n == 1: return1 ans = [1for _ inrange(n+1)]
for i inrange(2, n+1): ans[i] = ans[i-1] + ans[i-2] return ans[n]
1048. Longest String Chain
Store the word and the length of longest chain in a dictionary. For each word in the sorted list by length, first initialize the length to be 1, then loop through each char and form substring by removing it. If this substring is in the dictionary, update the key of this word, and update the final result.
Run time: $O(n\log n)+O(NSS)$, where word length $S\leq 16$, space: $O(NS)$
[Recursively delete leave nodes in a multi-tree](https://leetcode.com/discuss/interview-question/1693416/google-onsite-recursively-delete-leave-nodes-in-a-multi-tree)
Did you consider Topological sort kind of solution? i.e. Kahn’s algo?
I think, from a first glance, that you could construct another adj list that shows the parents per node, not the children (that is O(n)).
such list will be something like this:
1:[]
2:[1]
5:[1]
3:[1]
7:[2]
4:[2]
8:[4]
9:[5]
You also save a dict with the number of children each node has. Something like this
1:3
2:2
3:0
4:1
5:1
7:0
8:0
9:0
You construct a queue with all nodes that don’t have parents (i.e. leaves). Those with length zero
Per node you pop from Q, you write its value to answer, but you also make sure to reduce 1 from it from the parents num of children. So if we just handled node 9, that is a leaf, we know that its parent is 5. So we go to 5 and see it has 1 children. We reduce 1, to 0, If the num of children is now zero, we add 5 to the queue…
That way we peal the onion layer by layer, and it’s O(N) for building the special structures, and O(N) in building the answer.
reverse = False # not sure about this condition while stack_left and stack_right: if reverse: node =stack_right.pop() insert_right(node.left) else: node =stack_left.pop() insert_left(node.right) if is_leaf(node): reverse = not reverse print(node.val)
104. Maximum Depth of Binary Tree
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6
defmaxDepth(self, root: Optional[TreeNode]) -> int: ifnot root: return0 left = self.maxDepth(root.left) right = self.maxDepth(root.right) returnmax(left, right)+1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classSolution(object): defmaxDepth(self, root): """ :type root: TreeNode :rtype: int """ if root isNone: return0 queue = [root] depth = 0 while queue: depth += 1 for i inrange(len(queue)): cur_root = queue.pop(0) if cur_root.left: queue.append(cur_root.left) if cur_root.right: queue.append(cur_root.right) return depth
deffindDuplicateSubtrees(self, root: Optional[TreeNode]) -> List[Optional[TreeNode]]: # Concept - We can use a hashmap to store the all the trees and their roots # Also advantage of using hashmap is you can easily find for the repeated # or duplicate trees hashmap = {} res = [] self.solve(root, hashmap)
# Once the hashmap is built, we can just travserse through the values # and find out the values that are greater than 1 are repeated for val, node in hashmap.values(): if val > 1: res.append(node) return res
defsolve(self, root, hashmap): ifnot root: return'X' a = self.solve(root.left, hashmap) b = self.solve(root.right, hashmap) # Pre-order tree representation for storing the tree temp = str(root.val) + ' ' + a + ' ' + b # Check if it is already in hashmap if temp notin hashmap: hashmap[temp] = [1, root] else: hashmap[temp][0] += 1 return temp
572. Subtree of Another Tree
DFS. Recursion.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12
defisSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool: defdfs(s, t): ifnot s andnot t: returnTrue ifnot s ornot t: returnFalse return s.val == t.val and dfs(s.left, t.left) and dfs(s.right, t.right)
ifnot root: return if root.val == subRoot.val and dfs(root, subRoot): returnTrue return self.isSubtree(root.left, subRoot) or \ self.isSubtree(root.right, subRoot)
Use queue.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defisSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool: defdfs(n, t): ifnot n andnot t: returnTrue ifnot n ornot t: returnFalse return n.val == t.val and \ dfs(n.left, t.left) and \ dfs(n.right, t.right)
queue = [root] while queue: n = queue.pop(0) if n.val == subRoot.val and dfs(n, subRoot): returnTrue if n.left: queue.append(n.left) if n.right: queue.append(n.right) returnFalse
Iterative, BFS. The first leaf node is the min depth.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defminDepth(self, root: Optional[TreeNode]) -> int: visit_queue = deque([(root, 1)]) whilelen(visit_queue) != 0: # BFS Traversal next_visit, cur_depth =visit_queue.popleft() if next_visit isNone: # empty node or empty tree continue if next_visit.left isNoneand next_visit.right isNone: # reach a leaf node # get the minimal depth of binary tree, early return return cur_depth #append left and right child into visit_queue, increase current depth by 1 visit_queue.append( (next_visit.left, cur_depth+1) ) visit_queue.append( (next_visit.right, cur_depth + 1) )
# depth 0 for empty-tree return0
173. Binary Search Tree Iterator
Push all left nodes to the stack. Once a node is popped from the stack, push its right node back to the stack, and return its value.
defrightSideView(self, root: Optional[TreeNode]) -> List[int]: view = [] ifnot root: return [] q = collections.deque() q.append((root, 0)) while q: curr, level = q.popleft() if level == 0ornot q or q[0][1] > level: view.append(curr.val) if curr.left: q.append((curr.left, level + 1)) if curr.right: q.append((curr.right, level + 1)) return view
987. Vertical Order Traversal of a Binary Tree
Use defaultdict instead of normal dict to get rid of key not exist case. Put <level, value> as value into the dictionary, use placement as key. In the end sort placement, and sort the level.
Run time: $O(n\log n)$, space: $O()$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defverticalTraversal(self, root: Optional[TreeNode]) -> List[List[int]]: defhelper(placement, level, node, dic): ifnot node: return dic[placement].append((level, node.val)) helper(placement-1, level+1, node.left, dic) helper(placement+1, level+1, node.right, dic) dic = defaultdict(list) helper(0,0,root,dic) res = [] for i insorted(dic.keys()): temp = [] for j insorted(dic[i]): temp.append(j[1]) res.append(temp) return res
938. Range Sum of BST
Recursion. Need to check if strictly less than or greater than the range, then call helper function recursively.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defrangeSumBST(self, root: Optional[TreeNode], low: int, high: int) -> int: self.rangeSum = 0 defhelper(node, lo, hi): if node: if lo <= node.val <= hi: self.rangeSum += node.val if lo < node.val: helper(node.left, lo, hi) if node.val < hi: helper(node.right, lo, hi) helper(root, low, high) return self.rangeSum
Iterative
1 2 3 4 5 6 7 8 9 10 11 12 13
defrangeSumBST(self, root: Optional[TreeNode], low: int, high: int) -> int: ans = 0 stack = [root] while stack: node = stack.pop() if node: if L <= node.val <= R: ans += node.val if L < node.val: stack.append(node.left) if node.val < R: stack.append(node.right) return ans
426. Convert Binary Search Tree to Sorted Doubly Linked List
deftreeToDoublyList(self, root: 'Optional[Node]') -> 'Optional[Node]': defhelper(node): """ Performs standard inorder traversal: left -> node -> right and links all nodes into DLL """ if node: # left helper(node.left) # node if self.last: # link the previous node (last) # with the current one (node) self.last.right = node node.left = self.last else: # keep the smallest node # to close DLL later on self.first = node self.last = node # right helper(node.right) ifnot root: returnNone # the smallest (first) and the largest (last) nodes self.first, self.last = None, None helper(root) # close DLL self.last.right = self.first self.first.left = self.last return self.first
defdelNodes(self, root: Optional[TreeNode], to_delete: List[int]) -> List[TreeNode]: ans = [] to_delete = set(to_delete) defhelper(node): ifnot node: returnNone node.left = helper(node.left) node.right = helper(node.right) # add children of a node that is to be deleted if node.val in to_delete: if node.left: ans.append(node.left) if node.right: ans.append(node.right) returnNone return node helper(root) # if root is not to be deleted then add it if root.val notin to_delete: ans.append(root) return ans
defdelNodes(self, root: Optional[TreeNode], to_delete: List[int]) -> List[TreeNode]: output = [] to_delete_set = set(to_delete) tree_queue = [root] while tree_queue: curr_tree = tree_queue.pop(0) ifnot curr_tree: continue if curr_tree.val in to_delete_set: tree_queue.append(curr_tree.left) tree_queue.append(curr_tree.right) continue node_queue = [curr_tree] while node_queue: curr_node = node_queue.pop(0) ifnot curr_node: continue if curr_node.left and curr_node.left.val in to_delete_set: tree_queue.append(curr_node.left.left) tree_queue.append(curr_node.left.right) curr_node.left = None if curr_node.right and curr_node.right.val in to_delete_set: tree_queue.append(curr_node.right.left) tree_queue.append(curr_node.right.right) curr_node.right = None node_queue.append(curr_node.left) node_queue.append(curr_node.right) output.append(curr_tree) return output
235. Lowest Common Ancestor of a Binary Search Tree
Recursion. Since the input tree is a binary search tree, we can utilize the property and determine the LCA. If both p and q values are smaller or greater than the current node val, we know that the LCA could be further reduced.
deflowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': node = root while node: if node.val < p.val and node.val < q.val: node = node.right elif node.val > p.val and node.val > q.val: node = node.left else: return node
637. Average of Levels in Binary Tree
> DFS. Create a list of list where the index is the height. If the current node height is greater than or equal to the output array length, then append an empty array. Then access the array at index height and append the value. Use list comprehension to get the average for each list.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defaverageOfLevels(self, root: Optional[TreeNode]) -> List[float]: defgetH(node, height): ifnot node: return if height >= len(self.ans): self.ans.append([]) self.ans[height].append(node.val) getH(node.left, height+1) getH(node.right, height+1) self.ans = [] getH(root, 0) return [sum(s)/len(s) for s in self.ans]
450. Delete Node in a BST
> There are three possible situations here :
- Node is a leaf, and one could delete it straightforward : node = null.
- Node is not a leaf and has a right child. Then the node could be replaced by its successor which is somewhere lower in the right subtree. Then one could proceed down recursively to delete the successor.
- Node is not a leaf, has no right child and has a left child. That means that its successor is somewhere upper in the tree but we don't want to go back. Let's use the predecessor here which is somewhere lower in the left subtree. The node could be replaced by its predecessor and then one could proceed down recursively to delete the predecessor.
Run time: $O(\log n)$, space: $O(h)$, where $h$ is the tree height, when $h=\log n$, it is a balanced tree.
Put the odd nodes in a linked list and the even nodes in another. Then link the evenList to the tail of the oddList.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defoddEvenList(self, head: Optional[ListNode]) -> Optional[ListNode]: d1 = odd = ListNode(0) d2 = even = ListNode(0) i = 1 while head: if i%2: odd.next, odd = head,head else: even.next, even = head,head head = head.next i += 1 odd.next, even.next = d2.next,None return d1.next
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defoddEvenList(self, head: Optional[ListNode]) -> Optional[ListNode]: ifnot head: return head odd = head even = head.next evenHead = even while even and even.next: odd.next = even.next odd = odd.next even.next = odd.next even = even.next odd.next = evenHead return head
101. Symmetric Tree
Iterative. Maintain a queue and pop two elements at a time and compare, then append their child nodes pairs to the queue.
return n1.val == n2.val and \ helper(n1.right, n2.left) and \ helper(n1.left, n2.right) return helper(root, root)
437. Path Sum III
Use a variable to track the current prefix sum and a hashmap “prefix sum -> how many times was it seen so far”. Parse the input structure and count the requested subarrays/submatrices/tree paths along the way with the help of that hashmap. How to count?
The logic is simple: the current prefix sum is curr_sum, and some elements before the prefix sum was curr_sum - target. All the elements in between sum up to curr_sum - (curr_sum - target) = target.
defhelper(self, node, origin, targets): ifnot node: return0 hit = 0 for t in targets: ifnot t-node.val: hit += 1# count if sum == target targets = [t-node.val for t in targets]+[origin] # update the targets return hit+self.helper(node.left, origin, targets)+self.helper(node.right, origin, targets)
543. Diameter of Binary Tree
we know that the longest path in the tree would consist of a node, its longest left branch, and its longest right branch. So, our algorithm to solve this problem will find the node where the sum of its longest left and right branches is maximized. This would hint at us to apply Depth-first search (DFS) to count each node’s branch lengths, because it would allow us to dive deep into the leaves first, and then start counting the edges upwards.
Bottom up. If node is None, return 0. Then check for its left and right child, if any of them returns -1, then it means the subtree is not balanced. Or if the difference is greater than 1, the subtree is also not balanced. In the end return 1 + max of left and right child.
Run time: $O()$, space: $O()$
1 2 3 4 5 6 7 8 9 10 11
defisBalanced(self, root: Optional[TreeNode]) -> bool: defcheck(root): ifnot root: return0 left = check(root.left) right = check(root.right) if left == -1or right == -1orabs(left - right) > 1: return -1 return1 + max(left, right) return check(root) != -1
if recursive calls before conditional check, then its bottom up. If recursive call after conditional check, its top down.
236. Lowest Common Ancestor of a Binary Tree
> Recursive solution, DFS
Run time: $O(n)$, space: $O(h)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
deflowestCommonAncestor(self, root, p, q): """ :type root: TreeNode :type p: TreeNode :type q: TreeNode :rtype: TreeNode """ ifnot root: returnNone if p == root or q == root: return root left = self.lowestCommonAncestor(root.left, p, q) right = self.lowestCommonAncestor(root.right, p, q) if left and right: return root elifnot left: return right elifnot right: return left
Iterative, BFS. Use a stack to keep track of each viewed node, and a parent dict to track a node’s parent. If p and q are not in the parent dict, keep popping from the stack, add to parent dict, and append to stack. Then we create a set called ancestors, keep finding p’s parent. Then find q’s parent until q is in the ancestors, then return q.
deflowestCommonAncestor(self, root, p, q): """ :type root: TreeNode :type p: TreeNode :type q: TreeNode :rtype: TreeNode """ stack = [root] parent_dict = {root: None} while p notin parent_dict or q notin parent_dict: node = stack.pop() if node.left: parent_dict[node.left] = node stack.append(node.left) if node.right: parent_dict[node.right] = node stack.append(node.right) ancestors = set() while p: ancestors.add(p) p = parent_dict[p] while q notin ancestors: ancestors.add(q) q = parent_dict[q] return q
1650. Lowest Common Ancestor of a Binary Tree III
> Use two pointers to keep track of the parents, until they point to the same node. When either of the pointers points to the root, i.e., parent is None, then set it to the other original node.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10
deflowestCommonAncestor(self, p, q): """ :type node: Node :rtype: Node """ p1, p2 = p, q while p1 != p2: p1 = p1.parent if p1.parent else q p2 = p2.parent if p2.parent else p return p1
314. Binary Tree Vertical Order Traversal
> BFS, obtain the range of the column indices, walk through the column range [min_column, max_column] and retrieve the results accordingly.
# Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right classSolution(object): defverticalOrder(self, root): """ :type root: TreeNode :rtype: List[List[int]] """ defhelper(node, level): if node isNone: return if node.left isnotNone: if level-1notin d: d[level-1] = [] d[level-1].append(node.left.val) if node.right isnotNone: if level+1notin d: d[level+1] = [] d[level+1].append(node.right.val) helper(node.left, level-1) helper(node.right, level+1) if root isNone: return [] d = {} d[0] = [root.val] helper(root, 0) ans = [] for k insorted(d): ans.append(d[k]) return ans
Trie
>
Run time: $O()$, space: $O()$
1
208. Implement Trie (Prefix Tree)
Maintain a dictionary and keep adding empty entries if the character does not exist, and update the pointer to the key.
definsert(self, word: str) -> None: curr_d = self.trie for c in word: if c notin curr_d: curr_d[c] = {} curr_d = curr_d[c] curr_d['-'] = True
defsearch(self, word: str) -> bool: curr_d = self.trie for c in word: if c notin curr_d: returnFalse curr_d = curr_d[c] return'-'in curr_d
defstartsWith(self, prefix: str) -> bool: curr_d = self.trie for c in prefix: if c notin curr_d: returnFalse curr_d = curr_d[c] returnTrue
Segment Tree
String
>
Run time: $O()$, space: $O()$
1
859. Buddy Strings
> If A.length() != B.length(): no possible swap
If A == B, we need swap two same characters. Check is duplicated char in A.
In other cases, we find index for A[i] != B[i]. There should be only 2 diffs and it's our one swap.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5
defbuddyStrings(self, A, B): iflen(A) != len(B): returnFalse if A == B andlen(set(A)) < len(A): returnTrue dif = [(a, b) for a, b inzip(A, B) if a != b] returnlen(dif) == 2and dif[0] == dif[1][::-1]
from collections import defaultdict classFile(object): def__init__(self, name): self.map = defaultdict(File) self.name = name self.value = -1 classFileSystem(object):
def__init__(self): self.root = File("")
defcreate(self, path, value): """ :type path: str :type value: int :rtype: bool """ array = path.split("/") cur = self.root for i inrange(1, len(array)): name = array[i] if name notin cur.map: if i==len(array)-1: cur.map[name] = File(name) else: returnFalse cur = cur.map[name] if cur.value!=-1: returnFalse cur.value = value returnTrue
defget(self, path): """ :type path: str :rtype: int """ cur = self.root array = path.split("/") for i inrange(1, len(array)): name = array[i] if name notin cur.map: return -1 cur = cur.map[name] return cur.value
# Your FileSystem object will be instantiated and called as such: # obj = FileSystem() # param_1 = obj.create(path,value) # param_2 = obj.get(path)
defmyAtoi(self, s: str) -> int: MIN_NUM = -2 ** 31 MAX_NUM = 2 ** 31 - 1 # trim the leading white space first s = s.strip() sign = 1 index = 0 num = 0 ifnot s: return num if s[0] == '-': sign = -1 index += 1 elif s[0] == '+': index += 1 while index < len(s) and s[index].isdigit(): curr_digit = ord(s[index]) - ord('0') if num > MAX_NUM // 10or (num == MAX_NUM // 10and curr_digit > 7): # here we do the check before adding current digit if sign == 1: return MAX_NUM else: return MIN_NUM num = num * 10 + curr_digit index += 1 num = sign * num return num
388. Longest Absolute File Path
Split the input by new line using input.splitlines(), then use lstrip('\t') to remove the new tabs on its left. This would be the directory or file name. If the name contains a dot, then it is a file, and we could update the max length using the current depth plus the length of name. The number of tabs represents the depth of a file. If it is not a file, we put the depth+1 as the key, get the current depth level, add name length and add 1 as the backslash in the file path.
defwordsTyping(self, sentence: List[str], rows: int, cols: int) -> int: s = ' '.join(sentence) + ' ' l = len(s) count = 0 for _ inrange(rows): count += cols ind = count % l if s[ind] == ' ': count += 1# move to next new position else: while count >= 0and s[count % l] != ' ': count -= 1 count += 1# move to next new position return count // l
defnumSplits(self, s: str) -> int: left_count = collections.Counter() right_count = collections.Counter(s) res = 0 for c in s: left_count[c] += 1 right_count[c] -= 1 if right_count[c] == 0: del right_count[c] iflen(left_count) == len(right_count): res += 1 return res
Run time: $O(m)$, space: $O(n)$, Let N = len(S) and M = len(indexes)
1 2 3 4 5 6 7 8 9 10 11 12
deffindReplaceString(self, s: str, indices: List[int], sources: List[str], targets: List[str]) -> str: # lookup = {i: (src, tgt) for i, src, tgt in zip(indexes, sources, targets)} lookup = dict(zip(indices, zip(sources, targets))) i, result = 0, "" while i < len(s): if i in lookup and s[i:].startswith(lookup[i][0]): result += lookup[i][1] i += len(lookup[i][0]) else: result += s[i] i += 1 return result
843. Guess the Word
Calculate the occurrences for each character at position i (6 in total). Then sort the list by the sum of the occurrences. For each guess, pop the last word and make a guess, then discard any word that does have the same number of matched characters returned from master.
deffindSecretWord(self, wordlist: List[str], master: 'Master') -> None: weights = [Counter(word[i] for word in wordlist) for i inrange(6)] wordlist.sort(key=lambda word: sum(weights[i][c] for i, c inenumerate(word))) while wordlist: word = wordlist.pop() matches = master.guess(word) wordlist = [candidate for candidate in wordlist if matches == sum(c1 == c2 for c1, c2 inzip(word, candidate))]
791. Custom Sort String
>
Run time: $O(\max(m,n))$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defcustomSortString(self, order: str, s: str) -> str: freq = Counter(s) #O(n) + O(1) # equivalent: # freq = {} # for ch in s: #O(n) where n is len(s) # freq[ch] = freq.get(ch, 0) + 1
chars = [] for ch in order: #O(m) where m is the len(order) chars.append(ch*freq.pop(ch, 0)) #O(1) for key in freq.keys(): #O(k) where k is the size of keys present in s but not in order chars.append(key*freq[key]) #O(1)
return"".join(chars)
766. Toeplitz Matrix
Run time: $O(mn)$, space: $O(1)$
1 2 3 4 5 6
defisToeplitzMatrix(self, matrix: List[List[int]]) -> bool: for i inrange(len(matrix)-1): for j inrange(len(matrix[0])-1): if matrix[i][j] != matrix[i + 1][j + 1]: returnFalse returnTrue
defisToeplitzMatrix(self, matrix: List[List[int]]) -> bool: ifnot matrix ornot matrix[0]: returnFalse # Create a deque tracking the expected values for the next row expected = deque(matrix[0]) # Iterate through all the remaining rows, verifying they align with the # expected row. for row_i inrange(1, len(matrix)): row = matrix[row_i] expected.pop() expected.appendleft(row[0]) # Only check from index 1 and down as we've just added index 0 to expected for col_i inrange(1, len(row)): if row[col_i] != expected[col_i]: returnFalse # If we've reached here, all diagonals aligned returnTrue
953. Verifying an Alien Dictionary
Two cases: 1. prev and curr in correct order but prev length is longer, 2. prev and curr not in order.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defisAlienSorted(self, words: List[str], order: str) -> bool: d = {} for i, c inenumerate(order): d[c] = i
for i inrange(1, len(words)): #flag = 1 here means inner loop is break out due to pre less than cur #flag = 0 here means inner loop done iteration but two strings length are not equal pre, cur, flag = words[i-1], words[i], 0 for j inrange(min(len(pre),len(cur))): if d[pre[j]] < d[cur[j]]: flag = 1 break elif d[pre[j]] > d[cur[j]]: returnFalse ifnot flag andlen(pre) > len(cur): returnFalse returnTrue
defisNumber(self, s: str) -> bool: seen_digit = seen_exponent = seen_dot = False for i, c inenumerate(s): if c.isdigit(): seen_digit = True elif c in"+-": if i > 0and s[i - 1] != "e"and s[i - 1] != "E": returnFalse elif c in"Ee": if seen_exponent ornot seen_digit: returnFalse seen_exponent = True seen_digit = False elif c == ".": if seen_dot or seen_exponent: returnFalse seen_dot = True else: returnFalse return seen_digit
Deterministic Finite Automaton (DFA). Initialize the DFA as an array of hash tables. Each hash table’s keys will be a character group, and the values will be the state it should transition to. We can use the indexes of the array to handle state transitions. Set the currentState = 0.
Iterate through the input. For each character, first determine what group it belongs to. Then, check if that group exists in the current state’s hash table. If it does, transition to the next state. Otherwise, return false.
At the end, check if we are currently in a valid end state: 1, 4, or 7.
classSolution(object): defisNumber(self, s): # This is the DFA we have designed above dfa = [ {"digit": 1, "sign": 2, "dot": 3}, {"digit": 1, "dot": 4, "exponent": 5}, {"digit": 1, "dot": 3}, {"digit": 4}, {"digit": 4, "exponent": 5}, {"sign": 6, "digit": 7}, {"digit": 7}, {"digit": 7} ] current_state = 0 for c in s: if c.isdigit(): group = "digit" elif c in ["+", "-"]: group = "sign" elif c in ["e", "E"]: group = "exponent" elif c == ".": group = "dot" else: returnFalse
if group notin dfa[current_state]: returnFalse current_state = dfa[current_state][group] return current_state in [1, 4, 7]
5. Longest Palindromic Substring
Helper function that gets the start and end indices. Then use max function and set key=lambda x: x[1] - x[0]).
Run time: $O(n)$, space: $O(1)$ or $O(n)$
1 2 3 4 5 6 7 8 9 10 11
deflongestPalindrome(self, s: str) -> str: deff(i, j): while i >= 0and j < len(s) and s[i] == s[j]: i -= 1 j += 1 return i, j i = j = 0 for k inrange(len(s)): i, j = max((i, j), f(k, k), f(k, k+1), key=lambda x: x[1] - x[0]) return s[i+1:j]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
deflongestPalindrome(self, s: str) -> str: defhelper(s, start, end): while start >= 0and end < len(s) and s[start] == s[end]: start -= 1 end += 1 return s[start+1:end] ifnot s: return"" longest = "" for i inrange(len(s)): curr_substr1 = helper(s, i, i) curr_substr2 = helper(s, i, i+1) iflen(curr_substr1) > len(longest): longest = curr_substr1 iflen(curr_substr2) > len(longest): longest = curr_substr2 return longest
3. Longest Substring Without Repeating Characters
Sliding window. We use a dictionary to store the character as the key, the last appear index has been seen so far as value. seen[charactor] = index
Move the pointer when you met a repeated character in your window.
Run time: $O(n)$, space: $O(m)$, where $n$ is the length of the input string, $m$ is the number of unique characters of the input.
1 2 3 4 5 6 7 8 9 10 11 12
deflengthOfLongestSubstring(self, s: str) -> int: used = {} max_length = start = 0 for i, c inenumerate(s): if c in used and start <= used[c]: start = used[c] + 1 else: max_length = max(max_length, i - start + 1)
used[c] = i
return max_length
Brute force.
Run time: $O(n^2)$, space: $O(min(m,n))$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
deflengthOfLongestSubstring(self, s: str) -> int: ifnot s: return0 count = 1 i = 0 while i < len(s): seen = set() seen.add(s[i]) j = i+1 while j < len(s) and s[j] notin seen: seen.add(s[j]) j += 1 count = max(count, j-i) i = j return count
647. Palindromic Substrings
> There are 2N-1 possible centers for the palindrome: we could have a center at S[0], between S[0] and S[1], at S[1], between S[1] and S[2], at S[2], etc.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defcountSubstrings(self, s: str) -> int: ifnot s: return0 n = len(s) count = 0 for i inrange(2*n-1): left = i//2 right = (i+1)//2 while left >= 0and right < n and s[left] == s[right]: left -= 1 right += 1 count += 1 return count
defcountSubstrings(self, s: str) -> int: defhelper(s, i): if i < 0or i >= len(s): return0 count = 1 left,right = i-1,i+1 while left >=0and right < len(s): if s[left] == s[right]: count += 1 left, right = left-1, right+1
if i+1 < len(s) and s[i] == s[i+1]: count += 1 left,right = i-1,i+2 while left >=0and right < len(s): if s[left] == s[right]: count += 1 left, right = left-1, right+1 return count ifnot s: return0 ans = 0 for i inrange(len(s)): ans += helper(s, i) return ans
415. Add Strings
> use `chr()` to convert integer to character. `ord()` converts a charactor to integer.
Run time: $O(max(two strings))$, space: $O(max(two strings))$
defaddStrings(self, num1, num2): """ :type num1: str :type num2: str :rtype: str """ ans = 0 digit = 0 i, j = len(num1)-1, len(num2)-1 while i >= 0and j >= 0: d1 = int(num1[i]) d2 = int(num2[j]) temp_sum = d1+d2 temp = 10**digit * (temp_sum % 10) + 10**(digit+1) *(temp_sum//10) ans += temp digit += 1 i, j = i-1, j-1 if i < 0and j < 0: returnstr(ans) elif i < 0: ans += (10**digit) * int(num2[:j+1]) elif j < 0: ans += (10**digit) * int(num1[:i+1]) returnstr(ans)
68. Text Justification
> Use one list for final output, one list for current line, one variable to keep track of current character count. Since the current length also represents the minimum space added between words, if the length of current line, plus current character count, plus current word length exceeds `maxWidth`, then we should not add this word, and need to add more space to make the words on the current line distributed evenly.
Therefore, we loop from 0 to `maxWidth-char_count`, which is the number of space to be added on this line, then we take modulo. For example, if there are three words on this line, and we need to add 5 space. We first get 0%(3-1), which is 0. Then we calculate 1%(3-1), which is 1. Then we calculate 2%(3-1), which is 0. Then we calculate 3%(3-1), which is 1. Then we calculate 4%(3-1), which is 0.
deffullJustify(self, words, maxWidth): """ :type words: List[str] :type maxWidth: int :rtype: List[str] """ ans = [] cur = [] char_count = 0 for w in words: iflen(cur) + char_count + len(w) > maxWidth: for i inrange(maxWidth - char_count): cur[i % (len(cur)-1or1)] += ' ' ans.append(''.join(cur)) cur = [] char_count = 0 cur += [w] char_count += len(w) # form last line by join with space and left justify to maxWidth using ljust (python method) # that means pad additional spaces to the right to make string length equal to maxWidth return ans + [' '.join(cur).ljust(maxWidth)]
deffullJustify(self, words: List[str], maxWidth: int) -> List[str]: result, current_list, num_of_letters = [],[], 0 # result -> stores final result output # current_list -> stores list of words which are traversed but not yet added to result # num_of_letters -> stores number of chars corresponding to words in current_list for word in words: # total no. of chars in current_list + total no. of chars in current word # + total no. of words ~= min. number of spaces between words if num_of_letters + len(word) + len(current_list) > maxWidth: # size will be used for module "magic" for round robin # we use max. 1 because atleast one word would be there and to avoid modulo by 0 size = max(1, len(current_list)-1) for i inrange(maxWidth-num_of_letters): # add space to each word in round robin fashion index = i%size current_list[index] += ' ' # add current line of words to the output result.append("".join(current_list)) current_list, num_of_letters = [], 0 # add current word to the list and add length to char count current_list.append(word) num_of_letters += len(word) # form last line by join with space and left justify to maxWidth using ljust (python method) # that means pad additional spaces to the right to make string length equal to maxWidth result.append(" ".join(current_list).ljust(maxWidth)) return result
408. Valid Word Abbreviation
> Use two pointers, one for index in the word, one for the abbr.
defvalidWordAbbreviation(self, word, abbr): """ :type word: str :type abbr: str :rtype: bool """ i = j = 0 while i < len(word) and j < len(abbr): if word[i] == abbr[j]: i += 1 j += 1 elif abbr[j] == "0": returnFalse elif abbr[j].isdigit(): k = j+1 while k < len(abbr) and abbr[k].isdigit(): k += 1 i += int(abbr[j:k]) j = k else: returnFalse return i == len(word) and j == len(abbr)
921. Minimum Add to Make Parentheses Valid
> Maintain a count of current ungrouped parentheses, and a count of parentheses needed to be inserted. If we see a (, increment the count, otherwise decrement the count if it is greater than 0, or increment the final count.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defminAddToMakeValid(self, s): """ :type s: str :rtype: int """ count = 0 ans = 0 for c in s: if c == '(': count += 1 elif count == 0: ans += 1 else: count -= 1 return ans + count
1249. Minimum Remove to Make Valid Parentheses
> Use stack to store "(" only, and when encounter ")" and stack is empty, record this index. If stack is not empy, then pop one "(". In the end union the stack and the list and skip these indices when building the final string.
defminRemoveToMakeValid(self, s): """ :type s: str :rtype: str """ defremove_invalid(s, good, bad): sb = [] bal = 0 for c in s: if c == good: bal += 1 if c == bad: if bal == 0: continue bal -= 1 sb.append(c) return"".join(sb) s = remove_invalid(s, "(", ")") s = remove_invalid(s[::-1], ")", "(") return s[::-1]
defminRemoveToMakeValid(self, s): """ :type s: str :rtype: str """ ind_remove = set() stack = [] for i, c inenumerate(s): if c notin"()": continue if c == "(": # add ( to stack stack.append(i) elifnot stack: # ) and stack empty, need to remove ind_remove.add(i) else: # ) and stack not empty, pop one stack.pop() ind_remove = ind_remove.union(set(stack)) sb = [] for i, c inenumerate(s): if i notin ind_remove: sb.append(c) return"".join(sb)
defremoveOnes(self, grid: List[List[int]]) -> bool: r1, r1_invert = grid[0], [1-val for val in grid[0]] for i inrange(1, len(grid)): if grid[i] != r1 and grid[i] != r1_invert: returnFalse returnTrue
593. Valid Square
The idea is to calculate all the distance between each two points, and sort them. In this way, we get 4 edges and 2 diagonals of the square in order. If the 4 edges equal to each other, that means it should be equilateral parallelogram. Finally, we check whether the 2 diagonals equal to each other so as to check if it’s a square.
defnumberToWords(self, num: int) -> str: to19 = 'One Two Three Four Five Six Seven Eight Nine Ten Eleven Twelve ' \ 'Thirteen Fourteen Fifteen Sixteen Seventeen Eighteen Nineteen'.split() tens = 'Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety'.split() thousand = 'Thousand Million Billion'.split() defword(num, idx=0): if num == 0: return [] if num < 20: return [to19[num-1]] if num < 100: return [tens[num//10-2]] + word(num%10) if num < 1000: return [to19[num//100-1]] + ['Hundred'] + word(num%100)
p, r = num//1000, num%1000 if p % 1000 !=0: space = [thousand[idx]] else: space = [] return word(p, idx+1) + space + word(r) return' '.join(word(num)) or'Zero'
defmaximumSwap(self, num: int) -> int: digits = list(str(num)) min_ind, max_ind, max_i = -1, -1, len(digits)-1 for i inrange(max_i-1, -1, -1): if digits[i] > digits[max_i]: max_i = i elif digits[i] < digits[max_i]: min_ind, max_ind = i, max_i
if min_ind > -1: digits[min_ind], digits[max_ind] = digits[max_ind], digits[min_ind] returnint(''.join(digits))
48. Rotate Image
> rotate the image by 90 degrees (clockwise) is equivalent to transpose + reflection.
Run time: $O(mn)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defrotate(self, matrix: List[List[int]]) -> None: """ Do not return anything, modify matrix in-place instead. """ deftranspose(ma): n = len(ma) for i inrange(n): for j inrange(i+1, n): ma[j][i], ma[i][j] = ma[i][j], ma[j][i] defreflect(ma): n = len(ma) for i inrange(n): for j inrange(n//2): ma[i][j], ma[i][-j-1] = ma[i][-j-1], ma[i][j]
transpose(matrix) reflect(matrix)
Directly rotate. ~ means invert, ~j = -j-1
1 2 3 4 5 6 7 8 9
defrotate(self, matrix: List[List[int]]) -> None: """ Do not return anything, modify matrix in-place instead. """ n = len(matrix) for i inrange(n//2): for j inrange(n - n//2): matrix[i][j], matrix[~j][i], matrix[~i][~j], matrix[j][~i] = \ matrix[~j][i], matrix[~i][~j], matrix[j][~i], matrix[i][j]
268. Missing Number
Gauss’ Formula. Compute expected sum and actual sum, the difference is the missing number.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5
defmissingNumber(self, nums: List[int]) -> int: n = len(nums) expected = n*(n+1) // 2 actual = sum(nums) return expected-actual
Bit Manipulation.
Intuition
We can harness the fact that XOR is its own inverse to find the missing element in linear time.
Algorithm
Because we know that nums contains nn numbers and that it is missing exactly one number on the range [0…n-1][0…n−1], we know that nn definitely replaces the missing number in nums. Therefore, if we initialize an integer to nn and XOR it with every index and value, we will be left with the missing number. Consider the following example (the values have been sorted for intuitive convenience, but need not be):
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5
defmissingNumber(self, nums: List[int]) -> int: missing = len(nums) for i, num inenumerate(nums): missing ^= i ^ num return missing
Initialize a set using the input array. Loop through the length of array + 1, if not in set, return this number.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6
defmissingNumber(self, nums: List[int]) -> int: num_set = set(nums) n = len(nums) + 1 for number inrange(n): if number notin num_set: return number
238. Product of Array Except Self
> The output array does not count toward the space complexity. Use original output array to find the cumulative sum from left, then use one variable to keep track of the cumulative sum from right, and multiply it to the output array.
Run time: $O(n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12
defproductExceptSelf(self, nums: List[int]) -> List[int]: n = len(nums) ans = [1] * n right = 1 for i inrange(1,n): # cumulative sum from left ans[i] = ans[i-1]*nums[i-1] for i inrange(n-2,-1,-1): # cumulative sum from right right *= nums[i+1] ans[i] *= right return ans
1 2 3 4 5 6 7 8 9 10 11 12 13
defproductExceptSelf(self, nums: List[int]) -> List[int]: n = len(nums) left, right = 1, 1 ans = [1]*n for i inrange(1,n): left *= nums[i-1] ans[i] = left for i inrange(n-2,-1,-1): right *= nums[i+1] ans[i] *= right
return ans
One array to track the cumulative sum from left, one array to track the cumulative sum from right. In the end, multiply them together on the same index.
Run time: $O(n)$, space: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12
defproductExceptSelf(self, nums: List[int]) -> List[int]: n = len(nums) left, right = [1]*n, [1]*n
for i inrange(1, n): left[i] = nums[i-1] * left[i-1] for i inrange(n-2,-1,-1): right[i] = nums[i+1] * right[i+1] print(left, right) for i inrange(n): left[i] *= right[i] return left
67. Add Binary
XOR: ^, AND: &, left-shifted: <<
Run time: $O(m+n)$, space: $O(max(m,n))$
1 2 3 4 5 6 7
defaddBinary(self, a: str, b: str) -> str: x, y = int(a,2), int(b,2) while y: ans = x ^ y carry = (x & y) << 1 x, y = ans, carry returnbin(x)[2:]
defaddBinary(self, a: str, b: str) -> str: ans = "" i,j = len(a)-1, len(b)-1 carry = 0 while i>= 0or j>=0or carry>0: if i>=0: carry += int(a[i]) if j>=0: carry += int(b[j]) if carry == 3: ans += "1" carry = 1 elif carry == 2: ans += "0" carry = 1 elif carry == 1: ans += "1" carry = 0 i,j = i-1,j-1 return ans[::-1]
69. Sqrt(x)
Newton method. $x_{n+1} = x_n - \frac{f(x_n)}{f’(x_n)}$, where $f(x) = x^2-a = 0$.
Run time: $O(\log n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9
defmySqrt(self, x): """ :type x: int :rtype: int """ a = x while a**2 > x: a = (a + x/a)//2 return a
binary search.
Run time: $O(\log n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defmySqrt(self, x): """ :type x: int :rtype: int """ if x == 0: return0 lo, hi = 1, x while lo <= hi: mid = lo + (hi-lo)//2 sqrt = x // mid if sqrt == mid: return mid elif sqrt < mid: hi = mid-1 else: lo = mid +1 return hi
50. Pow(x, n)
First check if power is 0, and check if power is negative. If so, flip the sign of the power and return the reciprocal. Then calculate the half of the power, and if the original power is odd, multiply by one more x.
Run time: $O(\log n)$, space: $O(1)$
1 2 3 4 5 6 7 8 9 10 11 12 13
defmyPow(self, x, n): """ :type x: float :type n: int :rtype: float """ if n == 0: return1 if n < 0: return1.0 / self.myPow(x, -n) half = self.myPow(x, n // 2) if n % 2 == 0: return half * half else: return half * half * x