150 Flashcards
You are given an n x n binary matrix grid where 1 represents land and 0 represents water.
An island is a 4-directionally connected group of 1’s not connected to any other 1’s. There are exactly two islands in grid.
You may change 0’s to 1’s to connect the two islands to form one island.
Return the smallest number of 0’s you must flip to connect the two islands.
Input: grid = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
Output: 1
class Solution: def shortestBridge(self, grid: List[List[int]]) -> int: visit = set() ROWS, COLS = len(grid), len(grid[0]) directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] #DFS is to populate visit set with 1s. def dfs(r, c): if r < 0 or r == ROWS or c < 0 or c == COLS or (r, c) in visit or grid[r][c] == 0: return visit.add((r, c)) for dr, dc in directions: dfs(r + dr, c + dc) #Multisource BFS from one island to the other def bfs(): q = deque(visit) res = 0 while q: for _ in range(len(q)): r, c = q.popleft() for dr, dc in directions: neiR, neiC = r + dr, c + dc #The out of bounds check is on neighbors, not on (r, c). We assume r c looks good before adding. if neiR < 0 or neiR == ROWS or neiC < 0 or neiC == COLS or (neiR, neiC) in visit: continue #do not check for grid[neiR][neiC] == 0, because we can flip 0s to 1s if grid[neiR][neiC] == 1: return res q.append((neiR, neiC)) visit.add((neiR, neiC)) res += 1 return res for r in range(ROWS): for c in range(COLS): if grid[r][c] == 1: dfs(r, c) return bfs() #O(nm) #O(nm)
Copy List Random Pointer(self, head)
class Node: def \_\_init\_\_(self, x: int, next: 'Node' = None, random: 'Node' = None): self.val = int(x) self.next = next self.random = random class Solution: def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': oldToCopy = {None: None} #none maps to none. cur = head while cur: copyNode = Node(cur.val) oldToCopy[cur] = copyNode cur = cur.next cur = head while cur: copyNode = oldToCopy[cur] copyNode.next = oldToCopy[cur.next] copyNode.random = oldToCopy[cur.random] cur = cur.next return oldToCopy[head] #Runtime: O(n) to copy all the nodes, then O(n) to set pointers #Space: O(n) for the mapping of old to new nodes.
Given a string s containing only three types of characters: ‘(‘, ‘)’ and ‘*’, return true if s is valid.
class Solution: def checkValidString(self, s: str) -> bool: leftMin, leftMax = 0, 0 for c in s: if c == "(": leftMin, leftMax = leftMin + 1, leftMax + 1 elif c == ")": leftMin, leftMax = leftMin - 1, leftMax - 1 else: #wild card. Close min, and open max leftMin, leftMax = leftMin - 1, leftMax + 1 if leftMax < 0: #Example: ")" leftMax is negative. return False if leftMin < 0: #(*)( this is false, but if we don't reset leftMin to zero, will return True since leftMin is 0 leftMin = 0 return leftMin == 0 #O(n) #O(1) #Greedy solution
Given a 1-indexed array of integers numbers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target number. Let these two numbers be numbers[index1] and numbers[index2] where 1 <= index1 < index2 <= numbers.length.
Return the indices of the two numbers, index1 and index2, added by one as an integer array [index1, index2] of length 2.
The tests are generated such that there is exactly one solution. You may not use the same element twice.
Your solution must use only constant extra space.
class Solution: def twoSum(self, numbers: List[int], target: int) -> List[int]: l, r = 0, len(numbers) - 1 while l < r: if numbers[l] + numbers[r] < target: l += 1 elif numbers[l] + numbers[r] > target: r -= 1 else: #Due to answer wanting one-indexing return [l + 1, r + 1] #O(n) #O(1)
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining.
Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.
class Solution: def trap(self, height: List[int]) -> int: if not height: return 0 l, r = 0, len(height) - 1 maxL, maxR = height[l], height[r] #We use the minimum of MAXIMUM heights to our left and right. res = 0 while l < r: if maxL < maxR: l += 1 # water = max(maxL - height[l], 0) res += water maxL = max(maxL, height[l]) else: r -= 1 water = max(maxR - height[r], 0) res += water maxR = max(maxR, height[r]) return res #O(n) #O(1) #We increment l and decrement r first BEFORE doing the computation, because: # 1) the boundaries cannot contain any water # 2) After incrementing, maxL and maxR hasn't been updated yet, so will properly reflect "left" and "right" of the current height tile.In other words... after pushing, we're at a state where maxL is to our left! This makes sense.. initially, zero water trapped at edges # 3) Compute the water and add to result. It's possible the computation is negative, so max it with 0. # 4) THEN we updated maxL and maxR. This is because now we have considered this tile of height as potentially max, so now new maxL and maxR will be used in computation. We use the max function here, BECAUSE the amount of water bounded is: we take the minimum of the #maximum heights to the left and right.
The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other.
Given an integer n, return all distinct solutions to the n-queens puzzle. You may return the answer in any order.
Each solution contains a distinct board configuration of the n-queens’ placement, where ‘Q’ and ‘.’ both indicate a queen and an empty space, respectively.
Input: n = 4
Output: [[“.Q..”,”…Q”,”Q…”,”..Q.”],[”..Q.”,”Q…”,”…Q”,”.Q..”]]
Explanation: There exist two distinct solutions to the 4-queens puzzle as shown above
class Solution: def solveNQueens(self, n: int) -> List[List[str]]: cols = set() posDiag = set() # r - c negDiag = set() # r + c board = [["."] * n for i in range(n)] res = [] def backtrack(r): if r == n: boardCopy = ["".join(row) for row in board] res.append(boardCopy) return for c in range(n): if c in cols or r - c in negDiag or r + c in posDiag: continue #Place the queen board[r][c] = "Q" cols.add(c) negDiag.add(r - c) posDiag.add(r + c) backtrack(r + 1) #backtrack board[r][c] = "." cols.remove(c) negDiag.remove(r - c) posDiag.remove(r + c) backtrack(0) return res #Runtime: O(n!) #n queens for first slot, n - 1 fow next row, etc... #Space: O(n ^ 2) for the board
Alice has some number of cards and she wants to rearrange the cards into groups so that each group is of size groupSize, and consists of groupSize consecutive cards.
Given an integer array hand where hand[i] is the value written on the ith card and an integer groupSize, return true if she can rearrange the cards, or false otherwise.
Input: hand = [1,2,3,6,2,3,4,7,8], groupSize = 3
Output: true
Explanation: Alice’s hand can be rearranged as [1,2,3],[2,3,4],[6,7,8]
class Solution: def isNStraightHand(self, hand: List[int], groupSize: int) -> bool: if len(hand) % groupSize != 0: return False counts = {} for n in hand: counts[n] = counts.get(n, 0) + 1 minHeap = list(counts.keys()) heapq.heapify(minHeap) while minHeap: first = minHeap[0] for card in range(first, first + groupSize): if card not in counts: #if the necessary card isn't in our counts, immediately return False return False counts[card] -= 1 if counts[card] == 0: #if at any point we're trying to pop from the middle, #return False since that means we have a smaller value #we can't make a straight from later on. #Also we need minHeap[0] NOT first. This is because #we can be making cards for a subsequent iteration where #first is not the minHeap[0] at that time. #Example: first = 2, but making cards for 3, 4 #which are both at count 1. We decrement 1 for card i = 3, #and then we see minHeap[0] == 3, so we pop from minHeap #We don't compare it against FIRST! if card != minHeap[0]: return False else: heapq.heappop(minHeap) return True #O(n log n) #primarily heap operations to assemble heap and pop from it #O(n) for the heap
Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules:
Each row must contain the digits 1-9 without repetition.
Each column must contain the digits 1-9 without repetition.
Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition.
Input: board =
[[“5”,”3”,”.”,”.”,”7”,”.”,”.”,”.”,”.”]
,[“6”,”.”,”.”,”1”,”9”,”5”,”.”,”.”,”.”]
,[”.”,”9”,”8”,”.”,”.”,”.”,”.”,”6”,”.”]
,[“8”,”.”,”.”,”.”,”6”,”.”,”.”,”.”,”3”]
,[“4”,”.”,”.”,”8”,”.”,”3”,”.”,”.”,”1”]
,[“7”,”.”,”.”,”.”,”2”,”.”,”.”,”.”,”6”]
,[”.”,”6”,”.”,”.”,”.”,”.”,”2”,”8”,”.”]
,[”.”,”.”,”.”,”4”,”1”,”9”,”.”,”.”,”5”]
,[”.”,”.”,”.”,”.”,”8”,”.”,”.”,”7”,”9”]]
Output: true
class Solution: def isValidSudoku(self, board: List[List[str]]) -> bool: # rows = defaultdict(set) #r -> values in that r # cols = defaultdict(set) #c - > values in tha c # square = defaultdict(set) #(r // 3, c // 3) - > values in that square rows = {i: set() for i in range(9)} cols = {i: set() for i in range(9)} square = {(i, j): set() for i in range(3) for j in range(3)} for r in range(9): for c in range(9): if board[r][c] == ".": continue if board[r][c] in rows[r] or board[r][c] in cols[c] or board[r][c] in square[(r // 3, c // 3)]: return False rows[r].add(board[r][c]) cols[c].add(board[r][c]) square[(r // 3, c // 3)].add(board[r][c]) return True #O(81) -> O(1) #O(81) -> O(1)
Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.
Implement the MinStack class:
MinStack() initializes the stack object.
void push(int val) pushes the element val onto the stack.
void pop() removes the element on the top of the stack.
int top() gets the top element of the stack.
int getMin() retrieves the minimum element in the stack.
You must implement a solution with O(1) time complexity for each function.
class MinStack: def \_\_init\_\_(self): self.stack = [] self.minStack = [] def push(self, val: int) -> None: self.stack.append(val) curMin = min(val, self.minStack[-1]) if self.minStack else val self.minStack.append(curMin) def pop(self) -> None: self.stack.pop() self.minStack.pop() def top(self) -> int: return self.stack[-1] def getMin(self) -> int: return self.minStack[-1] #O(1) per op #O(n) space for both Your MinStack object will be instantiated and called as such: # obj = MinStack() # obj.push(val) # obj.pop() # param_3 = obj.top() # param_4 = obj.getMin()
Given an array of integers nums which is sorted in ascending order, and an integer target, write a function to search target in nums. If target exists, then return its index. Otherwise, return -1.
You must write an algorithm with O(log n) runtime complexity.
class Solution: def search(self, nums: List[int], target: int) -> int: l, r = 0, len(nums) - 1 while l <= r: mid = l + (r - l) // 2 if nums[mid] == target: return mid elif nums[mid] < target: l = mid + 1 else: r = mid - 1 return -1 #O(log n) #O(1)
You are given an array of strings tokens that represents an arithmetic expression in a Reverse Polish Notation.
Evaluate the expression. Return an integer that represents the value of the expression.
Note that:
The valid operators are ‘+’, ‘-‘, ‘*’, and ‘/’.
Each operand may be an integer or another expression.
The division between two integers always truncates toward zero.
There will not be any division by zero.
The input represents a valid arithmetic expression in a reverse polish notation.
The answer and all the intermediate calculations can be represented in a 32-bit integer.
class Solution: def evalRPN(self, tokens: List[str]) -> int: stack = [] for c in tokens: if c == "+": stack.append(stack.pop() + stack.pop()) elif c == "*": stack.append(stack.pop() * stack.pop()) elif c == "-": second = stack.pop() first = stack.pop() stack.append(first - second) elif c == "/": second = stack.pop() first = stack.pop() stack.append(int(first / second)) else: stack.append(int(c)) return stack[0] #O(n) #O(n)
You are given an m x n grid where each cell can have one of three values:
0 representing an empty cell,
1 representing a fresh orange, or
2 representing a rotten orange.
Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten.
Return the minimum number of minutes that must elapse until no cell has a fresh orange. If this is impossible, return -1.
Input: grid = [[2,1,1],[1,1,0],[0,1,1]]
Output: 4
class Solution: def orangesRotting(self, grid: List[List[int]]) -> int: ROWS, COLS = len(grid), len(grid[0]) q = deque() fresh = 0 for r in range(ROWS): for c in range(COLS): if grid[r][c] == 1: fresh += 1 if grid[r][c] == 2: q.append((r, c)) directions = [[0, 1], [0, -1], [1, 0], [-1, 0]] time = 0 #Mistake here: Need AND instead of or. This is because if unreachable orange, this will be stuck #in infinite loop #No need for visit set, because the number of reachable fresh oranges turning rotten will limit our queue and exit us. while q and fresh > 0: for _ in range(len(q)): r, c = q.popleft() for dr, dc in directions: neiR, neiC = r + dr, c + dc if neiR < 0 or neiR == ROWS or neiC < 0 or neiC == COLS: continue if grid[neiR][neiC] == 1: grid[neiR][neiC] = 2 fresh -= 1 q.append((neiR, neiC)) time += 1 return time if fresh == 0 else -1 #O(mn) for prework init and BFS #O(mn) since we have a q
Given the head of a linked list, reverse the nodes of the list k at a time, and return the modified list.
k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes, in the end, should remain as it is.
You may not alter the values in the list’s nodes, only nodes themselves may be changed
Input: head = [1,2,3,4,5], k = 2
Output: [2,1,4,3,5]
# Definition for singly-linked list. # class ListNode: # def \_\_init\_\_(self, val=0, next=None): # self.val = val # self.next = next class Solution: def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]: dummy = ListNode(0, head) groupPrev = dummy while True: #Don't use cur. Use groupPrev so that you can probably move along! #1) Find the kth node. kth = self.getKth(groupPrev, k) if not kth: break #2) Reverse everything in the k group, and make sure the final node points to groupNext instead of None # So for example, dummy 2->1->3->4->5 groupNext = kth.next prev, cur = kth.next, groupPrev.next #Instead of cur: (cur != None) while cur != groupNext: tmp = cur.next cur.next = prev prev = cur cur = tmp #The node to assign groupPrev to is groupPrev.next #NOT k.next. This is because k group can be of size more than 2... #3) From dummy 2->1->3->4->5, we want to #Point grouPrev(dummy) to 2 (kth), leading to dummy->2->1->3->4->5 #Have groupPrev be updated to 1. To get this, that was the original groupPrev.next tmp = groupPrev.next groupPrev.next = kth groupPrev = tmp return dummy.next def getKth(self, node, k): while node and k > 0: node = node.next k -= 1 return node #O(n) #O(1)
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
Input: n = 3
Output: [”((()))”,”(()())”,”(())()”,”()(())”,”()()()”]
class Solution: def generateParenthesis(self, n: int) -> List[str]: stack = [] res = [] #Can add left if open < n #Can add closed if closed < open #Valid result if open == closed == n def backtrack(openCount, closedCount): if openCount == closedCount == n: #no need to .copy() since we're OK with a global stack. We don't append the stack value, but instead the string, and that string won't change!! "".join(stack) res.append("".join(stack)) return if openCount < n: stack.append("(") backtrack(openCount + 1, closedCount) stack.pop() if closedCount < openCount: stack.append(")") backtrack(openCount, closedCount + 1) stack.pop() backtrack(0, 0) return res #Runtime: O(2^(2n) #2 choices of open or closed at each step of decision tree, and 2n steps -> O(4^n) #Space: O(2*n) for the height - the recursion stack
A linked list of length n is given such that each node contains an additional random pointer, which could point to any node in the list, or null. Construct a deep copy of the list. The deep copy should consist of exactly n brand new nodes, where each new node has its value set to the value of its corresponding original node. Both the next and random pointer of the new nodes should point to new nodes in the copied list such that the pointers in the original list and copied list represent the same list state. None of the pointers in the new list should point to nodes in the original list. For example, if there are two nodes X and Y in the original list, where X.random --> Y, then for the corresponding two nodes x and y in the copied list, x.random --> y. Return the head of the copied linked list. The linked list is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where: val: an integer representing Node.val random_index: the index of the node (range from 0 to n-1) that the random pointer points to, or null if it does not point to any node. Your code will only be given the head of the original linked list.
Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]
""" # Definition for a Node. class Node: def \_\_init\_\_(self, x: int, next: 'Node' = None, random: 'Node' = None): self.val = int(x) self.next = next self.random = random """ class Solution: def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': oldToCopy = {None: None} cur = head while cur: copyNode = Node(cur.val) oldToCopy[cur] = copyNode cur = cur.next cur = head while cur: copyNode = oldToCopy[cur] copyNode.next = oldToCopy[cur.next] copyNode.random = oldToCopy[cur.random] cur = cur.next return oldToCopy[head] #O(n) for pointer traversal and init of hashmap #O(n) for storage of all the nodes mapped to copies
Given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive.
There is only one repeated number in nums, return this repeated number.
You must solve the problem without modifying the array nums and uses only constant extra space.
Input: nums = [1,3,4,2,2]
Output: 2
class Solution: def findDuplicate(self, nums: List[int]) -> int: slow, fast = 0, 0 #think of the values of the array as pointers to INDICES #of the array #So 2 showing pointing up twice means that index 2 is pointed to twice by index 3 and 4 respectively. #So the draw the graph: index i points to nums[i] #0->1, 1->3, 2->4, 3->2, 4->2 #slow and fast are indices - find collision for Floyd while True: #this one, you can't do while fast and fast.next, so just do while True. slow = nums[slow] fast = nums[nums[fast]] if slow == fast: break slow2 = 0 while slow != slow2: slow = nums[slow] slow2 = nums[slow2] return slow #O(n) for pointers traversals #O(1) no extra mem #This is a LL problem because nothing points back to 0 #which is where we start, since everything is [1, n]. #So a cycle exists and the first chunk is always not a cycle.
Koko loves to eat bananas. There are n piles of bananas, the ith pile has piles[i] bananas. The guards have gone and will come back in h hours.
Koko can decide her bananas-per-hour eating speed of k. Each hour, she chooses some pile of bananas and eats k bananas from that pile. If the pile has less than k bananas, she eats all of them instead and will not eat any more bananas during this hour.
Koko likes to eat slowly but still wants to finish eating all the bananas before the guards return.
Return the minimum integer k such that she can eat all the bananas within h hours.
Input: piles = [3,6,7,11], h = 8
Output: 4
class Solution: def minEatingSpeed(self, piles: List[int], h: int) -> int: l, r = 1, max(piles) k = max(piles) #or inf while l <= r: mid = l + (r - l) // 2 numHours = 0 for p in piles: numHours += ceil(p / mid) #need to eat faster if numHours > h: l = mid + 1 else: #Set the best k so far k = mid #can also do min(mid, k) but unneeded. r = mid - 1 return k #O(# piles * log(max(piles))) for binary search #O(1) no additional pointers
Given the root of a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom.
Input: root = [1,2,3,null,5,null,4]
Output: [1,3,4]
# Definition for a binary tree node. # class TreeNode: # def \_\_init\_\_(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def rightSideView(self, root: Optional[TreeNode]) -> List[int]: if not root: return [] q = deque([root]) res = [] while q: rightVal = None for _ in range(len(q)): cur = q.popleft() rightVal = cur.val if cur.left: q.append(cur.left) if cur.right: q.append(cur.right) res.append(rightVal) return res #O(N) for bfs traversal number of nodes #O(N) space for the queue.
Given a binary tree root, a node X in the tree is named good if in the path from root to X there are no nodes with a value greater than X.
Return the number of good nodes in the binary tree.
Input: root = [3,1,4,3,null,1,5]
Output: 4
Explanation: Nodes in blue are good.
Root Node (3) is always a good node.
Node 4 -> (3,4) is the maximum value in the path starting from the root.
Node 5 -> (3,4,5) is the maximum value in the path
Node 3 -> (3,1,3) is the maximum value in the path.
# Definition for a binary tree node. # class TreeNode: # def \_\_init\_\_(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def goodNodes(self, root: TreeNode) -> int: def dfs(root, maxSoFar): if not root: return 0 curCount = 1 if root.val >= maxSoFar else 0 maxVal = max(root.val, maxSoFar) leftCount = dfs(root.left, maxVal) rightCount = dfs(root.right, maxVal) return curCount + leftCount + rightCount return dfs(root, root.val) #Can just pass in root.val for maxSoFar. Ok to be >= #Runtime: O(N) -> DFS traverse all the nodes #Space: O(h) -> recursion stack to the bottom
Given a binary tree, determine if it is
height-balanced
.
Output: true Input: root = [3,9,20,null,null,15,7]
Definition for a binary tree node.
# class TreeNode: # def \_\_init\_\_(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def isBalanced(self, root: Optional[TreeNode]) -> bool: #This dfs returns the height and whether it is balanced def dfs(root): if not root: return [0, True] leftHeight, leftBalanced = dfs(root.left) rightHeight, rightBalanced = dfs(root.right) curHeight = 1 + max(leftHeight, rightHeight) curBalanced = leftBalanced and rightBalanced and abs(leftHeight - rightHeight) <= 1 return [curHeight, curBalanced] return dfs(root)[1] #O(N) for dfs traversal and calculation of heights and balanced values #O(H) for the recursion stack to go down the tree
Given the root of a binary tree, return the length of the diameter of the tree.
The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root.
The length of a path between two nodes is represented by the number of edges between them
Input: root = [1,2,3,4,5]
Output: 3
Explanation: 3 is the length of the path [4,2,1,3] or [5,2,1,3].
# Definition for a binary tree node. # class TreeNode: # def \_\_init\_\_(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: #This problem is similar to binary tree max path sum, but this counts #number of edges instead of the sum of the node values #returns the number of edges up to the root, without splitting maxDiameter = [0] def dfs(root): if not root: return -1 #Max diameter passing through this root is the sum of the edges #from left -> root + right -> root (WITH SPLIT) #A node with only two children returns 0 leftHeight, rightHeight = dfs(root.left), dfs(root.right) maxDiameter[0] = max(maxDiameter[0], 2 + leftHeight + rightHeight) #Only 1 edge to either left or right (no split) return 1 + max(leftHeight, rightHeight) dfs(root) return maxDiameter[0] #O(N) all nodes #O(H) for recursion stack
Given an array of integers temperatures represents the daily temperatures, return an array answer such that answer[i] is the number of days you have to wait after the ith day to get a warmer temperature. If there is no future day for which this is possible, keep answer[i] == 0 instead.
Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]
class Solution: def dailyTemperatures(self, temperatures: List[int]) -> List[int]: stack = [] #two values: index, curTemp res = [0] * len(temperatures) for i, curTemp in enumerate(temperatures): while stack and stack[-1][1] < curTemp: stackIdx, stackTemp = stack.pop() res[stackIdx] = i - stackIdx stack.append((i, curTemp)) return res #O(n) traverse only once, push and pop stack only once #O(n) for the stack
Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth distinct element. Implement KthLargest class: KthLargest(int k, int[] nums) Initializes the object with the integer k and the stream of integers nums. int add(int val) Appends the integer val to the stream and returns the element representing the kth largest element in the stream.
Input
[“KthLargest”, “add”, “add”, “add”, “add”, “add”]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
Output
[null, 4, 5, 5, 8, 8]
Explanation
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3); // return 4
kthLargest.add(5); // return 5
kthLargest.add(10); // return 5
kthLargest.add(9); // return 8
kthLargest.add(4); // return 8
class KthLargest: def \_\_init\_\_(self, k: int, nums: List[int]): #minHeap of size k. Minimum is the kth largest element self.k = k self.minHeap = nums heapq.heapify(self.minHeap) while len(self.minHeap) > k: heapq.heappop(self.minHeap) def add(self, val: int) -> int: heapq.heappush(self.minHeap, val) if len(self.minHeap) > self.k: heapq.heappop(self.minHeap) return self.minHeap[0] #Runtime: O(n) for init if call heapify, O(log n) every add operation #Space: O(n) for the minHeap Your KthLargest object will be instantiated and called as such: # obj = KthLargest(k, nums) # param_1 = obj.add(val)
You are given an array of integers stones where stones[i] is the weight of the ith stone.
We are playing a game with the stones. On each turn, we choose the heaviest two stones and smash them together. Suppose the heaviest two stones have weights x and y with x <= y. The result of this smash is:
If x == y, both stones are destroyed, and
If x != y, the stone of weight x is destroyed, and the stone of weight y has new weight y - x.
At the end of the game, there is at most one stone left.
Return the weight of the last remaining stone. If there are no stones left, return 0.
class Solution: def lastStoneWeight(self, stones: List[int]) -> int: maxHeap = [-s for s in stones] heapq.heapify(maxHeap) while len(maxHeap) > 1: heaviest = -heapq.heappop(maxHeap) secondHeaviest = -heapq.heappop(maxHeap) diff = heaviest - secondHeaviest if diff > 0: heapq.heappush(maxHeap, -diff) return -maxHeap[0] if maxHeap else 0 #Runtime: O(n log n) - O(n) heap init, n pops of heap #Space: O(n) for heap
In this problem, a tree is an undirected graph that is connected and has no cycles.
You are given a graph that started as a tree with n nodes labeled from 1 to n, with one additional edge added. The added edge has two different vertices chosen from 1 to n, and was not an edge that already existed. The graph is represented as an array edges of length n where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the graph.
Return an edge that can be removed so that the resulting graph is a tree of n nodes. If there are multiple answers, return the answer that occurs last in the input.
class Solution: def findRedundantConnection(self, edges: List[List[int]]) -> List[int]: n = len(edges) rank = [1] * (n + 1) #nodes go from 1 to n. par = [i for i in range(n + 1)] def find(n1): p = par[n1] while p != par[p]: #Path compression par[p] = par[par[p]] p = par[p] return p def union(n1, n2): p1, p2 = find(n1), find(n2) if p1 == p2: #Already unioned, so this is a redundant edge return False if rank[p1] < rank[p2]: rank[p2] += rank[p1] par[p1] = p2 else: rank[p1] += rank[p2] par[p2] = p1 return True for u, v in edges: if not union(u, v): return (u, v) # O(E) same as O(V) in this problem. #Runtime: O(V + E*(inverse ackerman V)) -> O(V + E) -> O(V) #For init is O(V). Then for each edge E, the find operation takes inverse ackermann #Space: O(V)
Given an m x n matrix board containing ‘X’ and ‘O’, capture all regions that are 4-directionally surrounded by ‘X’.
A region is captured by flipping all ‘O’s into ‘X’s in that surrounded region.
Input: board = [[“X”,”X”,”X”,”X”],[“X”,”O”,”O”,”X”],[“X”,”X”,”O”,”X”],[“X”,”O”,”X”,”X”]]
Output: [[“X”,”X”,”X”,”X”],[“X”,”X”,”X”,”X”],[“X”,”X”,”X”,”X”],[“X”,”O”,”X”,”X”]]
Explanation: Notice that an ‘O’ should not be flipped if:
- It is on the border, or
- It is adjacent to an ‘O’ that should not be flipped.
The bottom ‘O’ is on the border, so it is not flipped.
The other three ‘O’ form a surrounded region, so they are flipped.
class Solution: def solve(self, board: List[List[str]]) -> None: """ Do not return anything, modify board in-place instead. """ ROWS, COLS = len(board), len(board[0]) #Key insight: Capture everything EXCEPT unsurrounded regions #No need for visit set, since exiting out is based on the values of the board #and you need to traverse this matrix multiple times. #Purpose of dfs: mark all unsurrounded regions with a "T" def dfs(r, c): if r < 0 or r == ROWS or c < 0 or c == COLS or board[r][c] != "O": return board[r][c] = "T" dfs(r - 1, c) dfs(r + 1, c) dfs(r, c - 1) dfs(r, c + 1) #1) Mark all unsurrounded regions (connected to border) as "O" -> "T" for r in range(ROWS): for c in range(COLS): if board[r][c] == "O" and (r in [0, ROWS - 1] or c in [0, COLS - 1]): dfs(r, c) #2) Flip all Os into Xs for r in range(ROWS): for c in range(COLS): if board[r][c] == "O": board[r][c] = "X" #3) Flip all Ts to Os. for r in range(ROWS): for c in range(COLS): if board[r][c] == "T": board[r][c] = "O" #Runtime: O(mn) for the dfs recursion, double for loops for marking etc #Space: O(mn) in theory for the recursion stack but actually much less than that in practice, since we're breaking out of cells early.
A triplet is an array of three integers. You are given a 2D integer array triplets, where triplets[i] = [ai, bi, ci] describes the ith triplet. You are also given an integer array target = [x, y, z] that describes the triplet you want to obtain.
To obtain target, you may apply the following operation on triplets any number of times (possibly zero):
Choose two indices (0-indexed) i and j (i != j) and update triplets[j] to become [max(ai, aj), max(bi, bj), max(ci, cj)].
For example, if triplets[i] = [2, 5, 3] and triplets[j] = [1, 7, 5], triplets[j] will be updated to [max(2, 1), max(5, 7), max(3, 5)] = [2, 7, 5].
Return true if it is possible to obtain the target triplet [x, y, z] as an element of triplets, or false otherwise.
Input: triplets = [[2,5,3],[1,8,4],[1,7,5]], target = [2,7,5]
Output: true
Explanation: Perform the following operations:
- Choose the first and last triplets [[2,5,3],[1,8,4],[1,7,5]]. Update the last triplet to be [max(2,1), max(5,7), max(3,5)] = [2,7,5]. triplets = [[2,5,3],[1,8,4],[2,7,5]]
The target triplet [2,7,5] is now an element of triplets.
class Solution: def mergeTriplets(self, triplets: List[List[int]], target: List[int]) -> bool: #represents which of the indices of target we have matched successfully. want #this to be size 3 haveIndices = set() for t in triplets: if t[0] > target[0] or t[1] > target[1] or t[2] > target[2]: continue for i, n in enumerate(t): if n == target[i]: haveIndices.add(i) return len(haveIndices) == 3 #O(n) runtime for all triplets #O(3) -> O(1) for the have indices set
You are given an m x n binary matrix grid. An island is a group of 1’s (representing land) connected 4-directionally (horizontal or vertical.) You may assume all four edges of the grid are surrounded by water.
The area of an island is the number of cells with a value 1 in the island.
Return the maximum area of an island in grid. If there is no island, return 0.
Input: grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
Output: 6
Explanation: The answer is not 11, because the island must be connected 4-directionally.
class Solution: def maxAreaOfIsland(self, grid: List[List[int]]) -> int: ROWS, COLS = len(grid), len(grid[0]) visit = set() directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] def dfs(r, c): if r < 0 or r == ROWS or c < 0 or c == COLS or (r, c) in visit or grid[r][c] == 0: return 0 #Add to visit set inside the DFS visit.add((r, c)) curArea = 1 for dr, dc in directions: curArea += dfs(r + dr, c + dc) return curArea res = 0 for r in range(ROWS): for c in range(COLS): #OK to not have any conditional here. This does help with eliminating unnecesasry dfs calls tho - see number of islands. if grid[r][c] == 1 and (r, c) not in visit: res = max(res, dfs(r, c)) return res #O(mn) for bfs traversal and double for loop #O(mn) for the recursion - let's say everything is an island.
There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai.
For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1.
Return the ordering of courses you should take to finish all courses. If there are many valid answers, return any of them. If it is impossible to finish all courses, return an empty array.
Input: numCourses = 2, prerequisites = [[1,0]]
Output: [0,1]
Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1].
class Solution: def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: prereqMap = {i: set() for i in range(numCourses)} for crs, pre in prerequisites: prereqMap[crs].add(pre) visit, cycle = set(), set() res = [] def dfs(c): if c in cycle: return False if c in visit: return True #currently adding this crs to the path cycle.add(c) for pre in prereqMap[c]: if not dfs(pre): #if any prereqs lead to cycles return False cycle.remove(c) #Was successful in visiting prereqs, so add cur to result res.append(c) visit.add(c) return True for crs in range(numCourses): if not dfs(crs): return [] return res #O(V + E) for traversal of all the nodes and edges and init of prereq map #O(V + E) for prereqMap init and traversal of all nodes and edges
You are given an m x n grid rooms initialized with these three possible values.
-1 A wall or an obstacle.
0 A gate.
INF Infinity means an empty room. We use the value 231 - 1 = 2147483647 to represent INF as you may assume that the distance to a gate is less than 2147483647.
Fill each empty room with the distance to its nearest gate. If it is impossible to reach a gate, it should be filled with INF.
Input: rooms = [[2147483647,-1,0,2147483647],[2147483647,2147483647,2147483647,-1],[2147483647,-1,2147483647,-1],[0,-1,2147483647,2147483647]]
Output: [[3,-1,0,1],[2,2,1,-1],[1,-1,2,-1],[0,-1,3,4]]
class Solution: def wallsAndGates(self, rooms: List[List[int]]) -> None: """ Do not return anything, modify rooms in-place instead. """ #Multisource BFS from all the gates. Starts at distance 0, increment per layer, fill out all the empty rooms with these level distances #And don't revisit cells, so we'll keep a visit set() ROWS, COLS = len(rooms), len(rooms[0]) visit = set() q = deque([]) #Initialize visit set and queue with the gates for r in range(ROWS): for c in range(COLS): if rooms[r][c] == 0: visit.add((r, c)) q.append((r, c)) directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] dist = 0 #initialize outside of queue, otherwise gets reset while q: for _ in range(len(q)): r, c = q.popleft() rooms[r][c] = dist #just set the distance. (r, c) here is either #1) a gate which we don't care about, because we initialize with gates, we'll never visit gates again and overwritten their values or #2) an empty room which we do care about for dr, dc in directions: neiR, neiC = r + dr, c + dc if neiR >= 0 and neiR < ROWS and neiC >= 0 and neiC < COLS and (neiR, neiC) not in visit and rooms[neiR][neiC] != -1: #is not a wall. visit.add((neiR, neiC)) q.append((neiR, neiC)) dist += 1 return rooms #O(mn) for init with gates, BFS traversal #O(mn) for visit set and q
Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.
A mapping of digits to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.
Input: digits = “23”
Output: [“ad”,”ae”,”af”,”bd”,”be”,”bf”,”cd”,”ce”,”cf”]
class Solution: def letterCombinations(self, digits: str) -> List[str]: if not digits: return [] digitToLetter = {"2": "abc", "3": "def", "4": "ghi", "5": "jkl", "6": "mno", "7": "pqrs", "8": "tuv", "9": "wxyz"} res = [] def dfs(i, curComb): if i == len(digits): res.append(curComb) return for c in digitToLetter[digits[i]]: dfs(i + 1, curComb + c) dfs(0, "") return res #Runtime: O(4^n * n): maximally 4 letter options for each digit, and takes n time to build the string when do string addition for the curComb, where n is length of digits string #Space: O(4^n * n) for result, or O(n) for recursion stack