These days, coding interviews are like security checks before getting into big companies, and they keep getting more competitive. It is not uncommon to hear someone fail coding rounds multiple times before eventually landing a decent job. LeetCode used to have only a few hundred questions; now it is enormous. I got dinged twice myself at the beginning of 2021, which is why I started rethinking what I should do about this trend, whether I like it or not.
I have heard many people, usually mid-senior level and often the losers of this coding game, argue that coding interviews are not reflective of a person's actual capability. There is truth to that. However, in a market-driven economy, coding interviews are still a proven and efficient way to screen people. Back when I was interviewing candidates at Bosch, I understood the pain of choosing among many qualified people. A coding round can act like an efficient weeder for the less prepared candidates. It is imperfect, but it is also practical.
The real question is how we should navigate a market that already works like this. Coding interview skills are not exactly the same as actual coding skills because the game is heavily algorithmic. In my years of doing ML research, I almost never needed a heap or fancy data structures to process my day-to-day work. Yet on a coding prep site, there is always a decent chance you will run into a heap question. Should I complain? Should I just keep doing research, publish papers, and stay in academia? Or should I get on the train and do LeetCode as well?
I am more of a realist, so my answer is to tackle LeetCode questions with a clear structure and to use that process to rebuild a stronger algorithmic foundation. ML, stats, or math skills are like the "devil fruit" power, but coding skills are still the basic combat power you rely on in a rapidly evolving tech world.
I am taking my baby steps.After doing LeetCode for 2+ years, I have several important epiphanies:
Array Questions are prevalent but could be combined with other categories Array questions I practiced
Stack questions on leetcode is not very intuitive. The questions, if not thought through carefully, can easily fool you into O(n^2) solutions. The correct answers are most of times O(n). Stack questions I practiced
while loop within the for loop that goes
through the input.
Heap questions are intrinsically more challenging. Leetcode lists almost all the heap questions above medium difficulty. But once you get used to them, everything would look very similar to you. Especially, once you are familiar with the idea of keeping priority queue in 2D heap and how to use 2 heaps to tackle a moving stream of data, all other heap questions would be similar derivative or variant questions. Heap questions I practiced
Tree questions are mostly revolving around BST(Binary Search Trees), and to grasp BST well, I really think the best way is to go back and read CLRS thoroughly and understand every bit they say about BSTs. Tree questions I practiced
Matrix questions are somehow related and they share lots of similarities. Matrix questions I practiced
Graph questions are involved, but I feel once I know the pattern, it's a lot more within control
def isBipartite(self, graph: List[List[int]]) -> bool:
length = len(graph)
parent = [i for i in range(length)]
def find(x):
if x!=parent[x]:
parent[x]=find(parent[x]) #rank compression
return parent[x]
def union(x,y): # union by rank can balance the tree
#whichever is taller tree will dominate
px,py=find(x),find(y)
if px!=py:
parent[px]=py
for i in range(length):
pari=find(i)
for j in graph[i]:
#check connected or not
if find(j)==pari:
return False
union(graph[i][0],j)
return True
def cloneGraph(self, node: 'Node') -> 'Node':
if not node:
return node
queue = [node]
seen = {}
seen[node] = Node(node.val, [])
while queue:
curr_node = queue.pop(0)
for neighbor in curr_node.neighbors:
if neighbor not in seen:
seen[neighbor] = Node(neighbor.val, [])
queue.append(neighbor)
seen[curr_node].neighbors.append(seen[neighbor])
return seen[node]
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if root is None:
return True
dfs_stack = [root]
self._add_all_left_nodes(root, dfs_stack)
prev_val = dfs_stack[-1].val - 1 # could use math.min as well
while len(dfs_stack) > 0:
node = dfs_stack.pop()
if prev_val < node.val:
prev_val = node.val
else:
return False
if node.right is not None:
dfs_stack.append(node.right)
self._add_all_left_nodes(node.right, dfs_stack)
return True
def _add_all_left_nodes(self, node, s):
while node.left is not None:
s.append(node.left)
node = node.left
Base Case; Transition function; How to think about breaking a problem down to subproblems. After doing quite a few of them, I found that starting with recursion is usually the easiest, then adding memoization is not difficult, and finally coming up with tabular dp will be natural. The core is to pin down the state-transition function as early as possible. DP questions I practiced
Backtracking is a tricky categrory, but could be solved using a template, 1.Subsets 2. Subsets II 3.Permutations 4. Permutations II 5. Combinations 6. Combination Sum II 7. Combination Sum III 8. Palindrome Partition Backtrack questions I practiced
def backtrack(candidate):
if find_solution(candidate):
output(candidate)
return
# iterate all possible candidates.
for next_candidate in list_of_candidates:
if is_valid(next_candidate):
# try this partial candidate solution
place(next_candidate)
# given the candidate, explore further.
backtrack(next_candidate)
# backtrack
remove(next_candidate)
Comments
Want to leave a comment? Visit this post's issue page on GitHub (you'll need a GitHub account).