⏳
Loading cheatsheet...
Arrays & Strings, Linked Lists, Stacks & Queues, Trees & Graphs, Sorting, Searching, Dynamic Programming — algorithm interview mastery.
# ── Two Pointers ──
# Two Sum II (sorted array)
def two_sum(nums: list[int], target: int) -> list[int]:
left, right = 0, len(nums) - 1
while left < right:
total = nums[left] + nums[right]
if total == target:
return [left + 1, right + 1]
elif total < target:
left += 1
else:
right -= 1
# Valid Palindrome
def is_palindrome(s: str) -> bool:
left, right = 0, len(s) - 1
while left < right:
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
# Container With Most Water
def max_area(height: list[int]) -> int:
left, right = 0, len(height) - 1
max_water = 0
while left < right:
width = right - left
h = min(height[left], height[right])
max_water = max(max_water, width * h)
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_water
# Three Sum
def three_sum(nums: list[int]) -> list[list[int]]:
res = []
nums.sort()
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
res.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
return res# ── Sliding Window ──
# Best Time to Buy and Sell Stock
def max_profit(prices: list[int]) -> int:
min_price = float('inf')
max_profit = 0
for price in prices:
min_price = min(min_price, price)
max_profit = max(max_profit, price - min_price)
return max_profit
# Longest Substring Without Repeating Characters
def length_of_longest_substring(s: str) -> int:
char_set = set()
left = 0
max_len = 0
for right in range(len(s)):
while s[right] in char_set:
char_set.remove(s[left])
left += 1
char_set.add(s[right])
max_len = max(max_len, right - left + 1)
return max_len
# Minimum Window Substring
def min_window(s: str, t: str) -> str:
if not s or not t or len(t) > len(s):
return ""
from collections import Counter
need = Counter(t)
missing = len(t)
left = start = 0
min_len = float('inf')
for right, ch in enumerate(s):
if ch in need:
need[ch] -= 1
if need[ch] >= 0:
missing -= 1
while missing == 0:
if right - left + 1 < min_len:
min_len = right - left + 1
start = left
if s[left] in need:
need[s[left]] += 1
if need[s[left]] > 0:
missing += 1
left += 1
return "" if min_len == float('inf') else s[start:start + min_len]# ── Prefix Sum ──
def prefix_sum(nums: list[int]) -> list[int]:
ps = [0]
for n in nums:
ps.append(ps[-1] + n)
return ps # ps[i] = sum of nums[0..i-1]
# Range sum query
def range_sum(ps: list[int], i: int, j: int) -> int:
return ps[j + 1] - ps[i] # sum of nums[i..j]
# Product of Array Except Self
def product_except_self(nums: list[int]) -> list[int]:
n = len(nums)
result = [1] * n
left = 1
for i in range(n):
result[i] = left
left *= nums[i]
right = 1
for i in range(n - 1, -1, -1):
result[i] *= right
right *= nums[i]
return result
# 2D Prefix Sum
def build_prefix_2d(matrix: list[list[int]]) -> list[list[int]]:
rows, cols = len(matrix), len(matrix[0])
ps = [[0] * (cols + 1) for _ in range(rows + 1)]
for i in range(1, rows + 1):
for j in range(1, cols + 1):
ps[i][j] = matrix[i-1][j-1] + ps[i-1][j] + ps[i][j-1] - ps[i-1][j-1]
return ps
def query_2d(ps, r1, c1, r2, c2):
return ps[r2+1][c2+1] - ps[r1][c2+1] - ps[r2+1][c1] + ps[r1][c1]| Pattern | When to Use | Examples |
|---|---|---|
| Two Pointers | Sorted arrays, palindrome, pairs | Two Sum, 3Sum, Palindrome |
| Sliding Window | Contiguous subarray/substring | Max subarray, Min window |
| Prefix Sum | Range sum queries | Range sum, 2D sum |
| Hash Map | Counting, lookup, frequency | Two Sum, Anagram |
| Binary Search | Sorted data, monotonic function | BS on answer, rotated array |
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
@classmethod
def from_list(cls, vals: list):
dummy = cls()
cur = dummy
for v in vals:
cur.next = cls(v)
cur = cur.next
return dummy.next
def to_list(self) -> list:
result = []
cur = self
while cur:
result.append(cur.val)
cur = cur.next
return result
# ── Reverse Linked List ──
def reverse_list(head: ListNode) -> ListNode:
prev = None
cur = head
while cur:
next_node = cur.next
cur.next = prev
prev = cur
cur = next_node
return prev
# ── Detect Cycle (Floyd's Tortoise & Hare) ──
def has_cycle(head: ListNode) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
# ── Merge Two Sorted Lists ──
def merge_two_lists(l1: ListNode, l2: ListNode) -> ListNode:
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
# ── Remove Nth Node From End ──
def remove_nth_from_end(head: ListNode, n: int) -> ListNode:
dummy = ListNode(0, head)
fast = slow = dummy
for _ in range(n + 1):
fast = fast.next
while fast:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummy.next
# ── Reorder List ──
def reorder_list(head: ListNode) -> None:
# Find middle
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
second = reverse_list(slow.next)
slow.next = None
# Merge
first = head
while second:
tmp1, tmp2 = first.next, second.next
first.next = second
second.next = tmp1
first, second = tmp1, tmp2# ── LRU Cache (Doubly Linked List + HashMap) ──
class Node:
def __init__(self, key=0, val=0):
self.key, self.val = key, val
self.prev = self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cap = capacity
self.cache = {} # key -> Node
self.head = Node() # dummy head
self.tail = Node() # dummy tail
self.head.next = self.tail
self.tail.prev = self.head
def _remove(self, node: Node):
node.prev.next = node.next
node.next.prev = node.prev
def _add_to_front(self, node: Node):
node.next = self.head.next
node.prev = self.head
self.head.next.prev = node
self.head.next = node
def get(self, key: int) -> int:
if key not in self.cache:
return -1
node = self.cache[key]
self._remove(node)
self._add_to_front(node)
return node.val
def put(self, key: int, value: int):
if key in self.cache:
self._remove(self.cache[key])
node = Node(key, value)
self._add_to_front(node)
self.cache[key] = node
if len(self.cache) > self.cap:
lru = self.tail.prev
self._remove(lru)
del self.cache[lru.key]
# ── Intersection of Two Linked Lists ──
def get_intersection_node(headA, headB):
if not headA or not headB:
return None
a, b = headA, headB
while a != b:
a = a.next if a else headB
b = b.next if b else headA
return a
# ── Palindrome Linked List ──
def is_palindrome_list(head: ListNode) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
second = reverse_list(slow)
first = head
result = True
while second:
if first.val != second.val:
result = False
break
first = first.next
second = second.next
return result| Pattern | Technique | Key Idea |
|---|---|---|
| Reverse | Iterative 3-pointer | prev, cur, next |
| Cycle | Slow/fast pointers | Floyd's algorithm |
| Middle | Slow/fast pointers | Fast moves 2x |
| Merge sorted | Dummy node | Compare & link |
| Nth from end | Fast advance N | Gap of N between pointers |
| Palindrome | Reverse half | Compare with reversed half |
| Operation | Array | Linked List |
|---|---|---|
| Access by index | O(1) | O(n) |
| Insert at head | O(n) | O(1) |
| Insert at tail | O(1)* | O(1) with tail ptr |
| Delete | O(n) | O(1) with node ref |
| Search | O(n) | O(n) |
| Reverse | O(n) | O(n) |
# ── Valid Parentheses ──
def is_valid(s: str) -> bool:
stack = []
mapping = {')': '(', ']': '[', '}': '{'}
for ch in s:
if ch in mapping:
top = stack.pop() if stack else '#'
if mapping[ch] != top:
return False
else:
stack.append(ch)
return not stack
# ── Min Stack (O(1) min) ──
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val: int):
self.stack.append(val)
self.min_stack.append(val if not self.min_stack else min(val, self.min_stack[-1]))
def pop(self):
self.min_stack.pop()
return self.stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
# ── Evaluate Reverse Polish Notation ──
def eval_rpn(tokens: list[str]) -> int:
stack = []
for t in tokens:
if t in '+-*/':
b, a = stack.pop(), stack.pop()
if t == '+': stack.append(a + b)
elif t == '-': stack.append(a - b)
elif t == '*': stack.append(a * b)
else: stack.append(int(a / b))
else:
stack.append(int(t))
return stack[0]
# ── Largest Rectangle in Histogram ──
def largest_rectangle(heights: list[int]) -> int:
stack = [] # indices of bars in increasing height
max_area = 0
heights.append(0) # sentinel
for i, h in enumerate(heights):
while stack and heights[stack[-1]] > h:
height = heights[stack.pop()]
width = i if not stack else i - stack[-1] - 1
max_area = max(max_area, height * width)
stack.append(i)
return max_areafrom collections import deque
# ── Sliding Window Maximum ──
def max_sliding_window(nums: list[int], k: int) -> list[int]:
dq = deque() # stores indices, decreasing values
result = []
for i, num in enumerate(nums):
while dq and nums[dq[-1]] < num:
dq.pop()
dq.append(i)
if dq[0] <= i - k:
dq.popleft()
if i >= k - 1:
result.append(nums[dq[0]])
return result
# ── Number of Islands (BFS) ──
def num_islands(grid: list[list[str]]) -> int:
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
count = 0
def bfs(r, c):
queue = deque([(r, c)])
grid[r][c] = '0'
while queue:
cr, cc = queue.popleft()
for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)]:
nr, nc = cr + dr, cc + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1':
grid[nr][nc] = '0'
queue.append((nr, nc))
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
bfs(r, c)
count += 1
return count
# ── Binary Tree Level Order Traversal (BFS) ──
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val, self.left, self.right = val, left, right
def level_order(root: TreeNode) -> list[list[int]]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
result.append(level)
return result
# ── Serialize & Deserialize Binary Tree (BFS) ──
def serialize(root):
if not root: return "[]"
queue = deque([root])
vals = []
while queue:
node = queue.popleft()
vals.append(str(node.val) if node else "null")
if node:
queue.append(node.left)
queue.append(node.right)
while vals and vals[-1] == "null":
vals.pop()
return "[" + ",".join(vals) + "]"| Feature | Stack (LIFO) | Queue (FIFO) |
|---|---|---|
| Add | push / append | enqueue / append |
| Remove | pop | popleft (deque) |
| Peek | top / [-1] | [0] |
| Use cases | Matching, DFS, expression eval | BFS, scheduling, buffering |
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val, self.left, self.right = val, left, right
# ── Inorder Traversal (Left, Root, Right) ──
def inorder(root: TreeNode) -> list[int]:
result = []
def dfs(node):
if not node: return
dfs(node.left)
result.append(node.val)
dfs(node.right)
dfs(root)
return result
# Iterative inorder
def inorder_iterative(root: TreeNode) -> list[int]:
result, stack = [], []
cur = root
while cur or stack:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
result.append(cur.val)
cur = cur.right
return result
# ── Max Depth of Binary Tree ──
def max_depth(root: TreeNode) -> int:
if not root: return 0
return 1 + max(max_depth(root.left), max_depth(root.right))
# ── Balanced Binary Tree ──
def is_balanced(root: TreeNode) -> bool:
def check(node):
if not node: return 0
left = check(node.left)
if left == -1: return -1
right = check(node.right)
if right == -1: return -1
if abs(left - right) > 1: return -1
return 1 + max(left, right)
return check(root) != -1
# ── Lowest Common Ancestor ──
def lowest_common_ancestor(root, p, q):
if not root or root == p or root == q:
return root
left = lowest_common_ancestor(root.left, p, q)
right = lowest_common_ancestor(root.right, p, q)
if left and right: return root
return left or right
# ── Validate BST ──
def is_valid_bst(root: TreeNode) -> bool:
def validate(node, low, high):
if not node: return True
if low is not None and node.val <= low: return False
if high is not None and node.val >= high: return False
return validate(node.left, low, node.val) and \
validate(node.right, node.val, high)
return validate(root, None, None)
# ── Flatten Binary Tree to Linked List ──
def flatten(root: TreeNode) -> None:
if not root: return
flatten(root.right)
flatten(root.left)
left = root.left
root.left = None
right = root.right
root.right = left
while left.right:
left = left.right
left.right = right# ── Graph Representations ──
# Adjacency list
graph = {
0: [1, 2],
1: [0, 3, 4],
2: [0, 4],
3: [1],
4: [1, 2],
}
# ── BFS (Shortest Path in unweighted graph) ──
from collections import deque
def bfs(graph, start):
visited = set([start])
queue = deque([start])
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return result
def shortest_path(graph, start, end):
visited = set([start])
queue = deque([(start, [start])])
while queue:
node, path = queue.popleft()
if node == end:
return path
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return []
# ── DFS ──
def dfs(graph, start):
visited = set()
result = []
def explore(node):
visited.add(node)
result.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
explore(neighbor)
explore(start)
return result
# ── Topological Sort ──
def topological_sort(num_courses, prerequisites):
graph = [[] for _ in range(num_courses)]
in_degree = [0] * num_courses
for course, prereq in prerequisites:
graph[prereq].append(course)
in_degree[course] += 1
queue = deque([i for i in range(num_courses) if in_degree[i] == 0])
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
return order if len(order) == num_courses else []
# ── Union-Find (Disjoint Set) ──
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # path compression
return self.parent[x]
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py: return False
if self.rank[px] < self.rank[py]: px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]: self.rank[px] += 1
return True| Feature | BFS | DFS |
|---|---|---|
| Data structure | Queue | Stack / Recursion |
| Space | O(width) | O(height) |
| Shortest path | Yes (unweighted) | No |
| Use cases | Level order, shortest path | Cycle detection, topological sort |
| Complete? | Level by level | Go deep first |
# ── Quick Sort ──
def quick_sort(arr: list[int]) -> list[int]:
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
# In-place quick sort
def quick_sort_inplace(arr, low=0, high=None):
if high is None: high = len(arr) - 1
if low < high:
pi = partition(arr, low, high)
quick_sort_inplace(arr, low, pi - 1)
quick_sort_inplace(arr, pi + 1, high)
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] < pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
# ── Merge Sort ──
def merge_sort(arr: list[int]) -> list[int]:
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i]); i += 1
else:
result.append(right[j]); j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# ── Heap Sort ──
import heapq
def heap_sort(arr: list[int]) -> list[int]:
heapq.heapify(arr) # min-heap in O(n)
return [heapq.heappop(arr) for _ in range(len(arr))]
def heap_sort_desc(arr: list[int]) -> list[int]:
# Use negative for max-heap
max_heap = [-x for x in arr]
heapq.heapify(max_heap)
return [-heapq.heappop(max_heap) for _ in range(len(max_heap))]
# ── Counting Sort (for small range) ──
def counting_sort(arr: list[int]) -> list[int]:
if not arr: return []
min_val, max_val = min(arr), max(arr)
count = [0] * (max_val - min_val + 1)
for num in arr:
count[num - min_val] += 1
result = []
for i, c in enumerate(count):
result.extend([i + min_val] * c)
return result| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | No |
| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
| Heap Sort | O(n log n) | O(n log n) | O(n log n) | O(1) | No |
| Tim Sort | O(n) | O(n log n) | O(n log n) | O(n) | Yes |
| Counting Sort | O(n+k) | O(n+k) | O(n+k) | O(n+k) | Yes |
| Radix Sort | O(dn) | O(dn) | O(dn) | O(n+d) | Yes |
# ── Binary Search (standard) ──
def binary_search(nums: list[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2 # prevent overflow
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# Lower bound (first >= target)
def lower_bound(nums: list[int], target: int) -> int:
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
return left
# Upper bound (first > target)
def upper_bound(nums: list[int], target: int) -> int:
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] <= target:
left = mid + 1
else:
right = mid
return left
# ── Search in Rotated Sorted Array ──
def search_rotated(nums: list[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
# Left half is sorted
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# Right half is sorted
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
# ── Find Min in Rotated Sorted Array ──
def find_min_rotated(nums: list[int]) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = left + (right - left) // 2
if nums[mid] > nums[right]:
left = mid + 1
else:
right = mid
return nums[left]# ── Binary Search on Answer ──
# Koko Eating Bananas
def min_eating_speed(piles: list[int], h: int) -> int:
lo, hi = 1, max(piles)
while lo < hi:
mid = lo + (hi - lo) // 2
hours = sum((p - 1) // mid + 1 for p in piles)
if hours <= h:
hi = mid
else:
lo = mid + 1
return lo
# ── Median of Two Sorted Arrays ──
def find_median_sorted_arrays(nums1: list[int], nums2: list[int]) -> float:
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
lo, hi = 0, m
while lo <= hi:
partition1 = (lo + hi) // 2
partition2 = (m + n + 1) // 2 - partition1
max_left1 = float('-inf') if partition1 == 0 else nums1[partition1 - 1]
min_right1 = float('inf') if partition1 == m else nums1[partition1]
max_left2 = float('-inf') if partition2 == 0 else nums2[partition2 - 1]
min_right2 = float('inf') if partition2 == n else nums2[partition2]
if max_left1 <= min_right2 and max_left2 <= min_right1:
if (m + n) % 2 == 0:
return (max(max_left1, max_left2) + min(min_right1, min_right2)) / 2
return float(max(max_left1, max_left2))
elif max_left1 > min_right2:
hi = partition1 - 1
else:
lo = partition1 + 1
return 0.0
# ── Intersection of Two Arrays (binary search) ──
def intersect_sorted(nums1: list[int], nums2: list[int]) -> list[int]:
nums1.sort(); nums2.sort()
i = j = 0
result = []
while i < len(nums1) and j < len(nums2):
if nums1[i] == nums2[j]:
result.append(nums1[i])
i += 1; j += 1
elif nums1[i] < nums2[j]:
i += 1
else:
j += 1
return result# ── Climbing Stairs ──
def climb_stairs(n: int) -> int:
if n <= 2: return n
a, b = 1, 2
for _ in range(3, n + 1):
a, b = b, a + b
return b
# ── House Robber ──
def rob(nums: list[int]) -> int:
prev2, prev1 = 0, 0
for num in nums:
curr = max(prev1, prev2 + num)
prev2, prev1 = prev1, curr
return prev1
# ── Coin Change (Minimum coins) ──
def coin_change(coins: list[int], amount: int) -> int:
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for i in range(coin, amount + 1):
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
# ── Word Break ──
def word_break(s: str, word_dict: list[str]) -> bool:
word_set = set(word_dict)
dp = [False] * (len(s) + 1)
dp[0] = True
for i in range(1, len(s) + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[len(s)]
# ── Longest Increasing Subsequence ──
def length_of_lis(nums: list[int]) -> int:
tails = [] # tails[i] = smallest tail of LIS of length i+1
for num in nums:
# Binary search for position
left, right = 0, len(tails)
while left < right:
mid = left + (right - left) // 2
if tails[mid] < num:
left = mid + 1
else:
right = mid
if left == len(tails):
tails.append(num)
else:
tails[left] = num
return len(tails)
# ── Edit Distance ──
def min_distance(word1: str, word2: str) -> int:
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]# ── Longest Common Subsequence ──
def longest_common_subsequence(text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
# ── 0/1 Knapsack ──
def knapsack(weights: list[int], values: list[int], capacity: int) -> int:
n = len(weights)
dp = [0] * (capacity + 1)
for i in range(n):
for w in range(capacity, weights[i] - 1, -1): # reverse!
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[capacity]
# ── Number of Paths (Grid) ──
def unique_paths(m: int, n: int) -> int:
dp = [[1] * n for _ in range(m)]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
# ── Minimum Path Sum ──
def min_path_sum(grid: list[list[int]]) -> int:
rows, cols = len(grid), len(grid[0])
dp = [[0] * cols for _ in range(rows)]
dp[0][0] = grid[0][0]
for j in range(1, cols): dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, rows): dp[i][0] = dp[i-1][0] + grid[i][0]
for i in range(1, rows):
for j in range(1, cols):
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
return dp[rows-1][cols-1]| Category | Pattern | Classic Problems |
|---|---|---|
| 1D DP | dp[i] depends on previous values | Climbing Stairs, House Robber |
| 2D DP | dp[i][j] grid/table | Edit Distance, LCS, Knapsack |
| String DP | Compare two strings | LCS, Edit Distance, Regex |
| Interval DP | dp[i][j] for substrings | Burst Balloons, Matrix Chain |
| Bitmask DP | dp[mask] for subsets | TSP, Assignment Problem |
| Tree DP | Post-order traversal | Diameter, House Robber III |
# ── Backtracking Template ──
def backtrack(path, choices):
if is_complete(path):
result.append(path[:])
return
for choice in choices:
if is_valid(choice):
path.append(choice)
backtrack(path, new_choices)
path.pop() # undo
# Subsets
def subsets(nums):
result = []
def backtrack(start, path):
result.append(path[:])
for i in range(start, len(nums)):
path.append(nums[i])
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return result
# Permutations
def permute(nums):
result = []
def backtrack(path, remaining):
if not remaining:
result.append(path[:])
return
for i in range(len(remaining)):
backtrack(path + [remaining[i]], remaining[:i] + remaining[i+1:])
backtrack([], nums)
return result
# Combination Sum
def combination_sum(candidates, target):
result = []
def backtrack(start, path, total):
if total == target:
result.append(path[:])
return
for i in range(start, len(candidates)):
if total + candidates[i] > target:
break
path.append(candidates[i])
backtrack(i, path, total + candidates[i]) # not i+1: reuse allowed
path.pop()
candidates.sort()
backtrack(0, [], 0)
return result
# ── Greedy Template ──
# Interval scheduling (maximum non-overlapping)
def erase_overlap_intervals(intervals):
if not intervals: return 0
intervals.sort(key=lambda x: x[1]) # sort by end
count = 1
end = intervals[0][1]
for start, e in intervals[1:]:
if start >= end:
count += 1
end = e
return len(intervals) - count # minimum to remove
# ── Monotonic Stack Template ──
# Next Greater Element
def next_greater(nums):
result = [-1] * len(nums)
stack = [] # decreasing stack
for i in range(len(nums)):
while stack and nums[stack[-1]] < nums[i]:
result[stack.pop()] = nums[i]
stack.append(i)
return result# ── Matrix Spiral Order ──
def spiral_order(matrix):
if not matrix: return []
result = []
top, bottom, left, right = 0, len(matrix)-1, 0, len(matrix[0])-1
while top <= bottom and left <= right:
for j in range(left, right + 1): result.append(matrix[top][j])
top += 1
for i in range(top, bottom + 1): result.append(matrix[i][right])
right -= 1
if top <= bottom:
for j in range(right, left - 1, -1): result.append(matrix[bottom][j])
bottom -= 1
if left <= right:
for i in range(bottom, top - 1, -1): result.append(matrix[i][left])
left += 1
return result
# ── Dijkstra's Algorithm ──
import heapq
def dijkstra(graph, start):
dist = {node: float('inf') for node in graph}
dist[start] = 0
pq = [(0, start)]
while pq:
d, node = heapq.heappop(pq)
if d > dist[node]:
continue
for neighbor, weight in graph[node]:
new_dist = d + weight
if new_dist < dist[neighbor]:
dist[neighbor] = new_dist
heapq.heappush(pq, (new_dist, neighbor))
return dist
# ── Trie (Prefix Tree) ──
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str):
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_end = True
def search(self, word: str) -> bool:
node = self.root
for ch in word:
if ch not in node.children:
return False
node = node.children[ch]
return node.is_end
def starts_with(self, prefix: str) -> bool:
node = self.root
for ch in prefix:
if ch not in node.children:
return False
node = node.children[ch]
return True| Pattern | Key Signal | Examples |
|---|---|---|
| Two Pointers | Sorted array, pairs | Two Sum II, Palindrome |
| Sliding Window | Contiguous subarray | Max subarray, Min window |
| Binary Search | Sorted data, monotonic | Rotated array, BS on answer |
| BFS | Shortest path, levels | Islands, Level order |
| DFS + Backtracking | All combinations | Subsets, Permutations, Sudoku |
| DP | Optimal substructure | Climbing stairs, LCS, Knapsack |
| Greedy | Local optimal = global | Interval scheduling |
| Monotonic Stack | Next greater/smaller | Temperature, Histogram |
| Trie | Prefix matching | Word search, Autocomplete |
| Union-Find | Connected components | Number of islands II |
| Complexity | Name | 10 | 100 | 1000 | 1M |
|---|---|---|---|---|---|
| O(1) | Constant | 10 | 100 | 1000 | 1M |
| O(log n) | Logarithmic | 3 | 7 | 10 | 20 |
| O(√n) | Square root | 3 | 10 | 32 | 1000 |
| O(n) | Linear | 10 | 100 | 1K | 1M |
| O(n log n) | Linearithmic | 33 | 664 | 10K | 20M |
| O(n²) | Quadratic | 100 | 10K | 1M | 1T |
| O(n³) | Cubic | 1K | 1M | 1B | - |
| O(2ⁿ) | Exponential | 1K | 1.3×10³⁰ | - | - |
| O(n!) | Factorial | 3.6M | - | - | - |
| Structure | Access | Search | Insert | Delete |
|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) |
| Hash Table | O(1)* | O(1)* | O(1)* | O(1)* |
| Balanced BST | O(log n) | O(log n) | O(log n) | O(log n) |
| Heap | O(1)* | O(n) | O(log n) | O(log n) |
| Trie | O(len) | O(len) | O(len) | O(len) |
*Amortized. Heap access is O(1) for peek, O(log n) for extract.
| N Size | O(n log n) | O(n²) | O(2ⁿ) |
|---|---|---|---|
| 10 | 33 ops | 100 ops | 1K ops |
| 100 | 664 ops | 10K ops | 1.3×10³⁰ ops |
| 1000 | 10K ops | 1M ops | Universe age |
| 10000 | 133K ops | 100M ops | Beyond comprehension |
| 100000 | 1.7M ops | 10B ops | Impossible |