Leetcode Cheatsheet¶
A list of algorithms I encountered while practicing LeetCode problems. Some are simply notes for reference, but others—such as DP logic and backtracking—are especially useful when you recognize that a problem requires a specific algorithm but can’t quite recall how to implement it. == :needs more practice, the more = the more practice needed
Array and Strings¶
Prefix Sum¶
(1D array)¶
def samplePrefix(arr:List[int])->int:
n = len(arr)
prefix = [0]*(n+1)
for i in range(n):
prefix[i+1] = prefix[i] + nums[i]
# sumofNum = prefix[r] - prefix[l], inclusive l and exclusive r
vector<int> samplePrefix(const vector<int>& arr) {
int n = arr.size();
vector<int> prefix(n + 1, 0); // prefix[0] = 0
for (int i = 0; i < n; i++) {
prefix[i + 1] = prefix[i] + arr[i];
}
return prefix;
}
(2D array)¶
def prefix2D(matrix):
ROW, COL = len(matrix), len(matrix[0])
prefix = [[0] * (COL + 1) for _ in range(ROW + 1)]
for r in range(1, ROW + 1):
for c in range(1, COL + 1):
prefix[r][c] = (matrix[r-1][c-1] # current cell
+ prefix[r-1][c] # sum of above
+ prefix[r][c-1] # sum of left
- prefix[r-1][c-1]) # remove overlap
return prefix
vector<vector<int>> prefix2D(const vector<vector<int>>& matrix) {
int ROW = matrix.size();
int COL = matrix[0].size();
vector<vector<int>> prefix(ROW + 1, vector<int>(COL + 1, 0));
for (int r = 1; r <= ROW; r++) {
for (int c = 1; c <= COL; c++) {
prefix[r][c] = matrix[r - 1][c - 1] // current cell
+ prefix[r - 1][c] // sum above
+ prefix[r][c - 1] // sum left
- prefix[r - 1][c - 1]; // remove overlap
}
}
return prefix;
}
Two Pointers¶
def twopt(nums, target): # find two num in sorted array that sum to target
l, r = 0, len(nums) - 1
while l < r:
s = nums[l] + nums[r]
if s == target:
return (l, r)
elif s < target:
l += 1
else:
r -= 1
return (-1, -1)
Sliding window¶
problems about subarrays/substrings with constraints
def slidingwindow(s, k): # longest substring with at most k distinct characters
l = 0
freq = Counter()
best = 0
for r, ch in enumerate(s):
freq[ch] += 1
while len(freq) > k:
freq[s[l]] -= 1
if freq[s[l]] == 0:
del freq[s[l]]
l += 1
best = max(best, r - l + 1)
return best
Sorting + Sweep line¶
problems about interval overlap, scheduling, max concurrency, skyline
def sweepline(intervals):
events = []
for s, e in intervals:
events.append((s, 1)) # +1 when interval starts
events.append((e, -1)) # -1 when interval ends
events.sort(key = lambda x: (x[0], x[1]))
active = 0
overlap = 0
for _, delta in evnets:
active += delta
overlap = max(overlap, active)
return overlap
Hashing¶
Counting¶
from collections import Couter
s = "hashing"
cnt = Counter(s)
Grouping¶
def grouping(strs): # eg. group anagrams
groups = defaultdict(list)
for s in strs:
key = tuple(sorted(s))
groups[key].append(s)
return list(groups.values())
Deduplicaiton¶
def dedup(nums): # only one of each can exist in hashset
unique = list(set(nums))
Prefix modulo¶
check if subarray sum is divisible by k, count number of subarray with sum divisible by k, detect cycles in cumulative sums with constraints
def prefixModulo(nums, k):
prefix = 0
seen = {0: -1}
for i, num in enumerate(nums):
prefix += num
remainder = prefix % k
if remainder in seen:
if i - seen[remainder] >= 2:
return True
else:
seen[remainder] = i
return False
Stack & Queue (v1)¶
Monotonic Stack¶
NGE =¶
def nge(nums): # next greater elements
n = len(nums)
res = [-1] * n
stack = []
for i in range(n):
while stack and nums[i] > nums[stack[-1]]:
idx = stack.pop()
res[idx] = nums[i]
stack.append(i)
return res
Largest Rectangle in Histogram =¶
def largestRec(heights):
stack = []
res = 0
for i, h in enumerate(heights):
start = i
while stack and h < stack[-1][1]:
idx, height = stack.pop()
res = max(res, height * (i - idx))
start = idx
stack.append((start, h))
for idx, height in stack:
res = max(res, height * (len(heights) - idx))
return res
Monotonic Queue¶
Sliding Window Maximum ==¶
def slidingWindowMax(nums, k):
dq, res = deque(), []
for i, num in enumerate(nums):
if dq and dq[0] <= i - k: # remove left index out of window
dq.popleft()
while dq and nums[dq[-1]] < num: # maintain decreasing order in deque
dq.pop()
dq.append(i)
if i >= k - 1:
res.append(nums[dq[0]])
return res
Linked Lists¶
Reverse Linked List¶
whole list
def reverseList(head:ListNode) -> ListNode:
prev = None
cur = head
while cur:
nxt = cur.next
cur.next = prev
prev = cur
cur = nxt
return prev
k-group
def reversekGroup(head:ListNode, k) -> ListNode:
def rev(start, end):
prev, cur = None, start
while cur != end:
nxt = xur.next
cur.next = prev
prev = cur
cur = nxt
return prev
dummy = ListNode(0, head)
groupPrev = dummy
while True:
# find kth node
kth = groupPrev
for _ in range(k):
kth = kth.next
if not kth:
return dummy.next
groupNext = kth.next
# Reverse the group
start = groupPrev.next
newHead = rev(start, groupNext)
# Reconnect
groupPrev.next = newHead
start.next = groupNext
groupPrev = start
Fast & Slow Pointers¶
find middle¶
def findMiddle(head: ListNode) -> ListNode:
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
cycle detection =¶
def cycleDetection(head: ListNode) -> bool:
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
Merge¶
def merge(l1, l2): # merge two sorted linked lists
dummy = ListNode()
cur = dummy
while l1 and l2:
if l1.val < l2.val:
cur.next, l1 = l1, l1.next
else:
cur.next, l2 = l2, l2.next
cur = cur.next
cur.next = l1 or l2
return dummy.next
Split¶
def split(head):
slow, fast = head, head
prev = None
while fast and fast.next:
prev, slow, fast = slow, slow.next, fast.next.next
if prev:
prev.next = None
return head, slow
Trees & Graphs¶
Tree’s Queue & BFS¶
from collections import deque
def bfs(root, target):
queue = deque()
step = 0
# initialize
queue.append(root)
# bfs
while queue:
size = len(queue)
for _ in range(size):
cur = queue.popleft()
if cur == target:
return step
for nxt in cur.neighbors:
queue.append(nxt)
step += 1
return -1 # no path from root to target
from collections import deque
def bfs(root, target):
queue = deque()
visit = set()
step = 0
# initialize
queue.append(root)
visit.add(root)
# bfs
while queue:
size = len(queue)
for _ in range(size):
cur = queue.popleft()
if cur == target:
return step
for nxt in cur.neighbots:
queue.append(nxt)
visit.add(nxt)
step += 1
return -1 # no path from root to target
Tree’s Stack & DFS¶
def DFS(root, target):
visited = set()
stack = []
visited.add(root)
stack.append(root)
while stack:
cur = stack.pop()
if cur == target:
return True
for nxt in cur.neighbors:
if nxt not in visited:
visited.add(nxt)
stack.append(nxt)
return False
Pre-order Traversal¶
def preorder(root):
if not root:
return []
res, stack = [], [root]
while stack:
node = stack.pop()
res.append(node.val)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return res
In-order Traversal¶
def inorder(root):
stack = []
cur = root
res = []
while cur or stack:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
Post-order Traversal¶
def postorder(root):
if not root:
return []
res, stack1, stack2 = [], [root], []
while stack1:
node = stack1.pop()
stack2.append(node)
if node.left:
stack1.append(node.left)
if node.right:
stack1.append(node.right)
while stack2:
res.append(stack2.pop().val)
return res
Lowest Common Ancestor ==¶
lowest (deepest) node in tree that has both p and q as descendants
def lowestCommonAncestor(root, p, q): # binary tree recursive not BST
if not root or root == p or root == q:
return root
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if left and right:
return root
return left or right
def lowestCommonAncestor(root, p, q): # binary tree iterative
parent = {root: None}
stack = [root]
while p not in parent or q not in parent:
node = stack.pop()
if node.left:
parent[node.left] = node
stack.append(node.left)
if node.right:
parent[node.right] = node
stack.append(node.right)
ancestors = set()
while p:
ancestors.add(p)
p = parent[p]
while q not in ancestors:
q = parent[q]
return q
Topological sort ====¶
DFS based Topological Sort =====¶
def topo(numNodes, edges):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
visited = [0] * numNodes
res = []
def dfs(node):
if visited[node] == 1:
return False
if visited[node] == 2:
return True
visited[node] = 1
for nei in graph[node]:
if not dfs(nei):
return False
visited[node] = 2
res.append(node)
return True
for i in range(numNodes):
if visited[i] == 0:
if not dfs(i):
return []
return res[::-1]
BFS based Topological Sort - Kahn’s Algorithm =====¶
def topoKahn(numNodes, edges):
graph = defaultdict(list)
indeg = [0] * numNodes
for u, v in edges:
graph[u].append(v)
indeg[v] += 1
q = deque([i for i in range(numNodes) if indeg[i] == 0])
res = []
while q:
node = q.popleft()
res.append(node)
for nei in graph[node]:
indeg[nei] -= 1
if indeg[nei] == 0:
q.append(nei)
Shortest path - Dijkstra ====¶
def dijkstra(n, edges, start):
graph = defaultdict(list)
for u, v, w in edges:
graph[u].append((v, w))
dist = [float('inf')] * n
dist[start] = 0
pq = [(0, start)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] == dist[u] + w
heapq.heappush(pq, (dist[v], v))
return dist
Shortest path - Bellman-Ford ====¶
def bellmanFord(n, edges, start):
dist = [float('inf')] * n
dist[start] = 0
for _ in range(n - 1):
updated = False
for u, v, w in edges:
if dist[u] != float('inf') and dist[u] + w < dist[v]:
dist[v] = dist[u] + w
updated = True
if not updated:
break
for u, v, w in edges:
if dist[u] != float('inf') and dist[u] + w < dist[v]:
return None
return dist
Disjoint Set Union ==¶
Implementation with constructor, find, and union. using path compression and union by rank
class UnionFind: # 有size的版本
def __init__(self, size):
self.root = [i for i in range(size)]
self.rank = [1] * size
self.count = size
def find(self, x):
if x == self.root[x]:
return x
self.root[x] = self.find(self.root[x])
return self.root[x]
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
self.root[rootX] = rootY
elif self.rank[rootX] > self.rank[rootY]:
self.root[rootY] = rootX
else:
self.root[rootY] = rootX
self.rank[rootX] += 1
self.count -= 1
def connected(self, x, y):
return self.find(x) == self.find(y)
class UnionFind: # 无size的版本
def __init__(self):
self.root = {}
self.rank = {}
self.count = 0
def find(self, x):
if x not in self.root:
self.root[x] = x
self.rank[x] = 1
self.count += 1
if x != self.root[x]:
self.root[x] = self.find(self.root[x])
return self.root[x]
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
self.root[rootX] = rootY
elif self.rank[rootX] > self.rank[rootY]:
self.root[rootY] = rootX
else:
self.root[rootY] = rootX
self.rank[rootX] += 1
self.count -= 1
def connected(self, x, y):
return self.find(x) == self.find(y)
MST - Kruskal’s Algorithm ===¶
Unionfind + seleced min edge from all edge, use union find to check if new edge would form a loop
# Class UnionFind ...###
def kruskal(n, edges):
uf = UnionFind(n)
mstWeight = 0 # MST = Minumum Spanning Tree
mstEdges = []
edges.sort()
for w, u, v in edges:
if uf.union(u, v):
mstWeight += w
mstEdges.append((u, v, w))
if len(mstEdges) == n - 1:
break
return mstWeight, mstEdges
MST - Prim’s Algorithm =====¶
find all points directly connected to the current mst, put all edges connect those points to current mst, and use min heap to add the edge to mst
def prim(graph, start = 0):
n = len(graph)
visited = [False] * n
mstEdges = []
totalCost = 0
minHeap = [(0, -1, start)]
while minHeap:
weight, u, v = heapq.heappop(minHeap)
if visited[v]:
continue
visited[v] = True
totalCost += weight
if u != -1:
mstEdges.append((u, v, weight))
for w, nei in graph[v]:
if not visited[nei]:
heapq.heappush(minHeap, (w, v, nei))
return totalCost, mstEdges
SPFA Algorithm =====¶
(bellman-ford + queue optimization)
def spfa(n, edges, source = 0):
INF = float('inf')
dist = [INF] * n
inQueue = [False] * n
dist[source = 0
queue = deque([source])
inQueue[source] = True
while queue:
u = queue.popleft()
inQueue[u] = False
for v, w in edges[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
if not inQueue[v]:
queue.append(v)
inQueue[v] = True
return dist
Binary Search¶
def binarySearch(nums, target): # 闭区间的做法
if len(nums) == 0:
return -1
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
# End Condition: left > right
return -1
def binarySearch(nums, target): # 左开右闭
if len(nums) == 0:
return -1
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid
# Post-processing:
# End Condition: left == right
if nums[left] == target:
return left
return -1
def binarySearch(nums, target): # 双开区间
if len(nums) == 0:
return -1
left, right = 0, len(nums) - 1
while left + 1 < right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid
else:
right = mid
# Post-processing:
# End Condition: left + 1 == right
if nums[left] == target: return left
if nums[right] == target: return right
return -1
Backtracking¶
回溯
def backtrack(candidate):
if find_solution(candidate):
output(candidate)
return
for next_candidate in list_of_candidates:
if is_valid(next_candidate):
place(next_candidate)
# 在新状态下递归,更深一层的解
backtrack(next_candidate)
# 回溯
remove(next_candidate)
Dynamic Programming —¶
DP思路-核心三要素
- some function or array that represents the answer for the problem for a given state - 设计某个函数或数组,表示某个状态下的答案
- transition between states - 状态之间的转移
- base case - 基础情况(边界条件)
定义好每个状态的含义,找到他们之间的转移关系,并设置好初始状态。
Linear DP¶
(note, do I really need to list those examples or can I just genrealize linear DP)
案例感觉有点重复,linear DP留一个应该就ok
House Robber =¶
def houseRobber(nums):
if not nums: return 0
n = len(nums)
if n == 1: return nums[0]
dp = [0] * n
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, n):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
return dp[-1]
Climbing Stairs =¶
def climbStairs(n):
if n <= 2: return n
dp = [0] * (n + 1)
dp[1], dp[2] = 1, 2
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
Stock =¶
def maxProfit(prices):
minPrice, maxProfit = float('inf'), 0
for p in prices:
minPrice = min(minPrice, p)
paxProfit = max(maxProfit, p - minPrice)
return maxProfit
Knapsack (0/1, complete, multi) —¶
0/1 Knapsack ==¶
def knapsack01(weights, values, W):
n = len(weights)
dp = [0] * (W + 1)
for i in range(n):
for w in range(W, weights[i] - 1, -1):
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[W]
Complete Knapsack ==¶
def knapsackComplete(weights, values, W):
n = len(weights)
dp = [0] * (W + 1)
for i in range(n):
for w in range(weights[i], W + 1):
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[W]
Multi Knapsack ==¶
def knapsackMulti(weights, values, counts, W):
items = []
for i in range(len(weights)):
c = counts[i]
k = 1
while k < c:
items.append((weights[i] * k, values[i] * k))
c -= k
k *= 2
if c > 0:
items.append((weights[i] * c, values[i] * c))
dp = [0] * (W + 1)
for w, v in items:
for j in range(W, w - 1, -1):
dp[j] = max(dp[j], dp[j - w] + v)
return dp[W]
Interval DP - burst balloons ====¶
state depends on splitting intervals into sub-intervals
def maxCoins(nums):
nums = [1] + nums + [1]
n = len(nums)
dp = [[0] * n for _ in range(n)]
for length in range(2, n):
for left in range(0, n - length):
right = left + length
for k in range(left + 1, right):
dp[left][rihgt] = max(dp[left][right], nums[left] * nums[k] * nums[right] + dp[left][k] + dp[k][right])
return dp[0][n - 1]
Sequence DP¶
comparing two sequences or subsequences
Longest Increasing Subsequence (LIS) ====¶
def lengthOfLIS(nums):
dp = [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
Longest Common Subsequence (LCS) =====¶
def lcs(text1, text2):
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m):
for j in range(n):
if text1[i] == text2[j]:
dp[i + 1][j + 1] = dp[i][j] + 1
else:
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j])
return dp[m][n]
Edit Distance =====¶
def minDistance(word1, word2):
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = 1 + min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
return dp[m][n]
Heaps/ Priority Queue —¶
String Algorithms —¶
KMP (short template + failure array) ==¶
Rabin-Karp Algorithm ==¶
def rabin_karp(haystack: str, needle: str) -> int:
if not needle:
return 0
if len(needle) > len(haystack):
return -1
RADIX = 256 # You can also use 26 if only lowercase letters
MOD = 10**9 + 7 # Large prime to avoid overflow
m, n = len(needle), len(haystack)
# Precompute RADIX^(m-1) % MOD for rolling hash
max_weight = pow(RADIX, m - 1, MOD)
# Helper to compute initial hash
def compute_hash(s):
h = 0
for c in s:
h = (h * RADIX + ord(c)) % MOD
return h
target_hash = compute_hash(needle)
window_hash = compute_hash(haystack[:m])
for i in range(n - m + 1):
if i > 0:
# Roll the hash: remove left char, add right char
window_hash = (
(window_hash - ord(haystack[i - 1]) * max_weight) * RADIX + ord(haystack[i + m - 1])
) % MOD
# Check hash and confirm match to handle collisions
if window_hash == target_hash and haystack[i:i + m] == needle:
return i
return -1
Bit Manipulation¶
Subset enumeration ==¶
def subsetEnumeration(nums): # use bit mask return all possible subset
n = len(nums)
res = []
for mask in range(1 << n):
if mask & (1 << i):
subset.append(nums[i])
res.append(subset)
return res
Counting set bits¶
popcount, use lowbit to repeatedly remove the lowest set bit
def countBits(x):
cnt = 0
while x:
x -= x & -x
cnt += 1
return cnt
Fenwick Tree (BIT) ====¶
Binary Indexed Tree
class BIT:
def __init__(self, n):
self.bit = [0] * (n + 1)
def update(self, i, delta):
while i < len(self.bit):
self.bit[i] += delta
i ++ i & -i
def query(self, i):
s = 0
while i > 0:
s ++ self.bit[i]
i -= i & -i
return s
Math¶
Fast Power ===¶
binary exponentiation with mod
def fastPow(base, exp, mod):
res = 1
base %= mod
while exp > 0:
if exp & 1:
res = (res * base) % mod
base = (base * base) % mode
exp >>= 1
return res
Sive of Eratosthesnes ==¶
generate primes up to n
def sieve(n): # cross out multiples when finding
isPrime = [True] * (n + 1)
isPrime[0] = isPrime[1] = False
for i in range(2, int(n**0.5) + 1):
if isPrime[i]:
for j in range(i * i, n + 1, i):
isPrime[j] = False
return [i for i, prime in enumerate(isPrime) if prime]
Extended Euclidean ====¶
for GCD & Bezout
def extendedEuclidean(a, b):
if b == 0:
return (a, 1, 0)
g, x1, y1 = extendedEuclidean(b, a % b)
return (g, y1, x1 - (a // b) * y1)
Fermat’s Little Theorem =====¶
modular inverse
def modinv(a, mod):
# Fermat's theorem: a ^ (p - 2) is inverse of a (p is prime)
return fastPow(a, mod - 2, mod)
nCr Mod Prime =====¶
combinatorics
MOD = 10**9 + 7
MAXN = 100000
fact = [1] * (MAXN + 1)
invfact = [1] * (MAXN + 1)
# factorial
for i in range(1, MAXN + 1):
fact[i] = fact[i-1] * i % MOD
# modular inverse factorial
def modinv(a, mod): return pow(a, mod-2, mod)
invfact[MAXN] = modinv(fact[MAXN], MOD)
for i in range(MAXN-1, -1, -1):
invfact[i] = invfact[i+1] * (i+1) % MOD
# Now nCr is O(1)
def nCr(n, r):
if r < 0 or r > n: return 0
return fact[n] * invfact[r] % MOD * invfact[n-r] % MOD
Matrix Exponentiation¶
technique to calculate powers of a transition matrix quickly, solving linear recurrences (Fibonacci, Tribonacci, etc)
def matMulti(A, B, mod = None):
n = len(A)
res = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
for k in range(n):
res[i][j] += A[i][k] * B[k][j]
if mod:
res[i][j] %= mod
return res
def matPow(M, power, mod = None):
n = len(M)
res = [[1 if i == j else 0 for j in range(n)] for i in range(n)]
while power > 0:
if power % 2 == 1:
res = matMulti(res, M, mod)
M = matMulti(M, M, mod)
power //= 2
return res
Engineering Nodes —¶
Code Style¶
Naming¶
Functions → snake_case
Classes → PascalCase
Constants → UPPER_SNAKE_CASE
Best Practives¶
Keep function short ( ≤ 30-40 lines ideally)
use type hints
use docstrings for functions
handle edge cases early
Common pitfalls checklist¶
Mutable defaults¶
def f(x, arr = []): ... # BAD
def f(x, arr = None): # GOOD
if arr is None: arr = []
Recursion depth errors¶
convert to iterative DP when depth ~ 10 ^ 5
Shadowing built ins¶
Don’t name variables “list”, “dict”, “id”, etc
Test cases not enough¶
Always test: empty input, single element, max constraints
Pytest Testing Usage¶
not applicable to leetcode
use fixtures, parametrized tests, and exception testing
Interviews and Tools¶
Interview Tips¶
Think out loud - explain reasoning step by step, even final code did not work, interviewer values clarity + approach
Start with Brute force - outline simple solution first, and then optimize
State invariants - mention what must stay true after each iteration or recursion
Edge case first - get edge case handled at the beginning of the function
Git Repo Notes¶
things to add later: Z-function/Manacher, segment tree